Reputation: 60634
I'm trying to test an OWIN Middleware component in an ASP.NET WebApi 2 application. The middleware is supposed to look at the cookies of the incoming request, modify some stuff on the request before it's handed to the next component, and potentially set a cookie on the way out as well.
What's tripping me up is that the OwinRequest.Cookies
property is of type RequestCookieCollection
, which doesn't seem to allow modifying, and the property itself is read-only which means I can't use the RequestCookieCollection(IDictionary<string,string>)
constructor to initialize a collection with cookies already in it and set that on the request.
I want do do something like this:
var context = new OwinContext();
// option 1:
context.Request.Cookies.Append("cookie-name", "cookie-value");
// option 2:
var cookies = new RequestCookieCollection(new Dictionary<string, string>{ { "cookie-name", "cookie-value" } });
context.Request.Cookies = cookies;
await myMiddleware.Invoke(context);
// Assert stuff about context.Response
but that doesn't work for aforementioned reasons.
I had hoped not to have to mock IOwinContext
entierly, because it's quite convenient to have it set up with well-functioning Request
and Response
objects (I need to, among other things, look at Request.User.Identity.IsAuthenticated
in my implementation).
Upvotes: 4
Views: 2940
Reputation: 247323
Using the following as the subject of an example middleware for unit testing
using RequestDelegate = Func<IOwinContext, Task>;
class MyMiddleware {
private readonly RequestDelegate next;
public MyMiddleware(RequestDelegate next) {
this.next = next;
}
public async Task Invoke(IOwinContext context) {
//The middleware is supposed to look at the cookies of the incoming request,
var request = context.Request;
var authenticated = request.User.Identity.IsAuthenticated;
var cookies = request.Cookies;
if (cookies["cookie-name"] != null) {
// 1. check for existence of a cookie on the incoming request.
//if the cookie exists, use its value to set a header,
//so that the pipline after this component thinks the header was always present.
request.Headers.Append("header-name", "header-value");
}
//2. call the next component.
await next.Invoke(context);
//3. on the way out, check for some conditions, and possibly set a cookie value.
if (authenticated) {
context.Response.Cookies.Append("cookie-name", "cookie-value");
}
}
}
and using the details provided about the desired behavior,
it would be difficult to unit test the controller with all the plumbing needed for a full OwinContext
.
You have already stated a lot of the limitations around accessing some of the members.
Owin however provides many abstractions that would allow for the desired behavior to be mock/stubbed/faked for an isolated unit test.
The following example is based on the subject provided above, and uses the Moq
mocking framework along with concrete implementations to properly setup and exercise an isolated unit test for the subject middleware.
[TestClass]
public class OwinMiddlewareCookiesTest {
[Test]
public async Task MyMiddleware_Should_Set_RequestHeader_And_ResponseHeader() {
//Arrange
var cookieStore = new Dictionary<string, string> { { "cookie-name", "cookie-value" } };
var cookies = new RequestCookieCollection(cookieStore);
var request = Mock.Of<IOwinRequest>();
var requestMock = Mock.Get(request);
requestMock.Setup(_ => _.Cookies).Returns(cookies);
requestMock.Setup(_ => _.User.Identity.IsAuthenticated).Returns(true);
requestMock.Setup(_ => _.Headers.Append(It.IsAny<string>(), It.IsAny<string>()));
var response = new OwinResponse();
var context = Mock.Of<OwinContext>();
var contextMock = Mock.Get(context);
contextMock.CallBase = true;
contextMock.Setup(_ => _.Request).Returns(request);
contextMock.Setup(_ => _.Response).Returns(response);
RequestDelegate next = _ => Task.FromResult((object)null);
var myMiddleware = new MyMiddleware(next);
//Act
await myMiddleware.Invoke(context);
//Assert
requestMock.Verify(_ => _.Headers.Append("header-name", "header-value"));
response.Headers.ContainsKey("Set-Cookie");
}
}
Only the necessary dependencies needed to be mocked for the test to flow to completion and the expected behavior verified.
And since the request implementation's Cookies
property was not able to be overridden, the abstraction had to be used. This however provided greater flexibility in mocking the desired behavior.
Upvotes: 1