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.
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.
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.
Based on the setup code provided(not sure if its a typo), I think it should read as follows
var Response = new Mock<HttpResponseBase>();
Response.Setup(r => r.OutputStream).Returns(new MemoryStream());
Response.Setup(r => r.Headers).Returns(new NameValueCollection());
精彩评论