Tomas Aschan
Tomas Aschan

Reputation: 60634

Testing OWIN Middleware in ASP.NET WebApi 2: How to set cookies?

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

Answers (1)

Nkosi
Nkosi

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

Related Questions