18

I wish to create an instance of HttpRequest with populated data so I can test a method.

I need to pass Microsoft.AspNetCore.Http.HttpRequest as a parameter to a function.

How do I instantiate it?

[Route("/summary/{id}")]
public IActionResult Account(int id)
{
   var summary = RequestHelper.ParseRequest(Request);
}

Options is from the package https://github.com/louthy/language-ext

public static Option<SummaryRequest> ParseRequest(HttpRequest request)
{
    if (request== null)
    {
        var query = request.Query;
  
        var result = new SummaryRequest();
    
        var locations = ExtractData(query, "location");
        var categories = ExtractData(query, "categories[]");
        var titles = ExtractData(query, "titles");
    
    }
}
    
public static Option<SummaryRequest> ParseRequest(HttpRequest request)
{
    if (request== null)
    {
        var query = request.Query;
            
        var result = new SummaryRequest();
            
        var locations = ExtractData(query, "location");
        var categories = ExtractData(query, "categories[]");
        var titles = ExtractData(query, "titles");
    }
           
    return new SummaryRequest();
}

public static Either<Exception, string[]> ExtractData(IEnumerable<KeyValuePair<String, StringValues>> query, string filter)
{
    try
    {
        return query.First(x => x.Key.ToLower() == filter).Value.ToString().Split(',').ToArray();
    }
    catch (Exception ex)
    {
         return ex;
    }
} 

Unit Test Example

 [TestMethod]
 [TestCategory(Test.RequestParser)]
 public void ParseRequest_WithHttpRequest_ReturnResultOnSuccess()
 {            
    // var request = this doesn't compile I need an instance of 
    //                 Microsoft.AspNetCore.Http.HttpRequest
        
     var result = Helper.ParseRequest(request);
 }
3
  • Since HttpRequest is an abstract type, you can create a mock and setup all expected properties Commented Nov 1, 2020 at 16:59
  • Have you considered using ASP.NET's model binding instead of processing the parameters manually, or only passing the query object to the ParseRequest method? The query should be somewhat simpler to stub as there is QueryCollection implementation since 3.1 that you can just populate with a dictionary. Alternatively, you can accept the wordy IEnumerable<KeyValuePair<String, StringValues>> and use dictionary directly. Commented Nov 1, 2020 at 17:22
  • Please take a look on the blog article mahmutcanga.com/2019/12/13/unit-testing-httprequest-in-c Commented Nov 1, 2020 at 19:05

2 Answers 2

25

You can use the Request property of DefaultHttpContext. It's a bit convoluted since you have to specify a lot of properties separately (compared to e.g. WebRequest.Create), but the following worked for me:

var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "POST";
httpContext.Request.Scheme = "http";
httpContext.Request.Host = new HostString("localhost");
httpContext.Request.ContentType = "application/json";
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
await writer.WriteAsync("{}");
await writer.FlushAsync();
stream.Position = 0;
httpContext.Request.Body = stream;
Sign up to request clarification or add additional context in comments.

Comments

0

Glorfindel's answer, although good enough for most scenarios fails at a critical one: The default HttpRequestStream which is the original stream beneath Request.Body is "forward-only" and if you want to read it twice, you need to use HttpContext.Request.EnableBuffering().

Now the question, how do you replicate this on a test scenario?: I created a stub implementation:

internal sealed class HttpRequestStreamStub : MemoryStream
{
    public HttpRequestStreamStub() { }
    public HttpRequestStreamStub(byte[] buffer) : base(buffer) { }

    public override bool CanSeek => false;

    public override bool CanRead => true;

    public override bool CanWrite => false;

    public override long Length => throw new NotSupportedException();

    public override long Position
    {
        get => throw new NotSupportedException();
        set => throw new NotSupportedException();
    }

    public override int WriteTimeout
    {
        get => throw new NotSupportedException();
        set => throw new NotSupportedException();
    }

    public override void Write(byte[] buffer, int offset, int count)
        => throw new NotSupportedException();

    public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        => throw new NotSupportedException();

    public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
        => throw new NotSupportedException();

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotSupportedException();
    }

    public override void SetLength(long value)
    {
        throw new NotSupportedException();
    }
}

it replicates as best as possible the behavior of https://github.com/dotnet/aspnetcore/blob/main/src/Servers/Kestrel/Core/src/Internal/Http/HttpRequestStream.cs

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.