Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpResponseBase.Headers are empty when running test

I'm using Moq to create a Mock<HttpResponseBase> to test an FileResult I'm creating for my MVC2 application.

In the WriteFile(HttpResponseBase response) method of the FileResult, I have the following code at the end:

// Write the final output with specific encoding.
response.OutputStream.Write(output, 0, output.Length);
response.AppendHeader("Content-Encoding", encoding);

It will use utf-8 or gzip depending on the encoding from the request's Accept-Encoding header.

So then in my test, I setup my Mock<HttpResponseBase> like so:

var mockResponse = new Mock<HttpResponseBase>();
mockResponse.Setup(r => r.OutputStream).Returns(new MemoryStream());
mockResponse.Setup(r => r.Headers).Returns(new NameValueCollection());

But when I actually check that the header has been set, Content-Encoding always returns null:

var response = mockResponse.Object;
Assert.AreEqual("utf-8", response.Headers["Content-Encoding"]);

The weird thing is that the OutputStream gets the data written to it and I can assert that it's writing the correct value.

The odd thing is that when I actually debug the FileResult in a web project, the header is properly sent.

Does anyone have some insight to this? I can provide more code if necessary.

like image 249
TheCloudlessSky Avatar asked Nov 01 '10 01:11

TheCloudlessSky


2 Answers

The issue you are having is to do with the fact that you are trying to partially mock HttpResponseBase class. Writing to output stream works because the property (OutputStream in this case) is mocked and is accessed from SUT (System Under Test).

However, when you mock Headers property, it is only that property that is getting mocked and not AppendHeader, which is what your SUT actually does. The default mocks created by Moq simply stub all methods and properties as returning default values, so AppendHeader doesn't actually do anything.

There are two solutions to this, first one is purely interaction testing and is my preferred approach. Do not mock Headers, but instead verify AppendHeader.

Mock<HttpResponseBase> responseMock = new Mock<HttpResponseBase>();
//the rest of response setup
FileResult sut = new MyFileResult();
sut.WriteFile(responseMock.Object);
responseMock.Verify(response=>response.AppendHeader("Content-Encoding", "utf8"));

Second approach is to utilise Moq's partial mocking, which would make the mock object call actual class' methods, unless they have been set up explicitly.

Mock<HttpResponseBase> responseMock = new Mock<HttpResponseBase>(){ CallBase = true };
//the rest of response setup
FileResult sut = new MyFileResult();
sut.WriteFile(responseMock.Object);
Assert.AreEqual("utf-8", responseMock.Object.Headers["Content-Encoding"]);

I would prefer the first version by a thin margin as you are testing your interaction with the framework. The second version is more of a state-based test, so theoretically you don't even need a mock, you could just use the actual class. In case of HttpResponseBase, however, it almost makes sense as its constructor is protected, so by creating a mock, you are basically deriving from it inline, without having to manually write a test double.

like image 80
Igor Zevaka Avatar answered Sep 24 '22 01:09

Igor Zevaka


I ended up just mocking the AppendHeader method to forcefully add the header to the HttpResponseBase's headers:

mockResponse
    .Setup(r => r.AppendHeader(It.IsAny<string>(), It.IsAny<string>()))
    .Callback((string k, string v) => mockResponse.Object.Headers.Add(k, v));

I suspect that there is something further down inside the call of AppendHeader that doesn't like adding headers without an actual HttpResponseBase in place.

If there is a better idea, feel free to suggest. Hope this helps someone in the future.

like image 42
TheCloudlessSky Avatar answered Sep 23 '22 01:09

TheCloudlessSky