Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access Cookies inside unit test in AspNet.TestHost.TestServer context on ASP.NET 5 / MVC 6

There is no easy way to get an access to a CookieContainer in response object running integration tests with AspNet.TestHost.TestServer. Cookies have to be set by the controller action. What is the best way to achieve that?

            var client = TestServer.Create(app =>
            {
                app.UseMvc(routes => 
                       routes.MapRoute("default", "{controller}/{action}/{id?}"));
                app.UseIdentity();
            }).CreateClient();

            var request = new HttpRequestMessage(HttpMethod.Get, "account/login");
            var response = await client.SendAsync(request);

            // how to get an access to cookie container ?????????
            // response.Cookies prop doesn't exist
            Assert.NotEmpty(response.Cookies["auth"]);

Solution that I see is to extend instance of the TestServer, return instance of a class CustomClientHandler : ClientHandler and override the whole process of sending a request in that handler, but it needs literally to change all logic except relatively small code of the TestServer.

Any better suggestion how to implement an access to Cookies in a response?

like image 438
pocheptsov Avatar asked Sep 23 '15 22:09

pocheptsov


3 Answers

For a dotnet core integration test approach like the one described in the docs here, you can get cookies with the following code:

public class CookieTests : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _factory;

    public CookieTests(WebApplicationFactory<Startup> factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task GetPage_ShouldSetCookie_CookieSet()
    {
        using (var client = _factory.CreateClient())
        {
            var response = await client.GetAsync("/cookie_setting_url");
            response.EnsureSuccessStatusCode();

            //or other assertions
            Assert.True(response.Headers.TryGetValues(HeaderNames.SetCookie, out IEnumerable<string> cookies));
        }
    }
}
like image 119
Matt Avatar answered Oct 25 '22 11:10

Matt


As an addition to @Oleh's response, you can achieve the same without reflection on newer frameworks like .NET 4.6.1+ / .NET Core

public class TestHttpClientHandler : DelegatingHandler
{
    [NotNull]
    private readonly CookieContainer cookies = new CookieContainer();

    public TestHttpClientHandler([NotNull] HttpMessageHandler innerHandler)
        : base(innerHandler) { }

    [NotNull, ItemNotNull]
    protected override async Task<HttpResponseMessage> SendAsync([NotNull] HttpRequestMessage request, CancellationToken ct)
    {
        Uri requestUri = request.RequestUri;
        request.Headers.Add(HeaderNames.Cookie, this.cookies.GetCookieHeader(requestUri));

        HttpResponseMessage response = await base.SendAsync(request, ct);

        if (response.Headers.TryGetValues(HeaderNames.SetCookie, out IEnumerable<string> setCookieHeaders))
        {
            foreach (SetCookieHeaderValue cookieHeader in SetCookieHeaderValue.ParseList(setCookieHeaders.ToList()))
            {
                Cookie cookie = new Cookie(cookieHeader.Name.Value, cookieHeader.Value.Value, cookieHeader.Path.Value);
                if (cookieHeader.Expires.HasValue)
                {
                    cookie.Expires = cookieHeader.Expires.Value.DateTime;
                }
                this.cookies.Add(requestUri, cookie);
            }
        }

        return response;
    }
}
like image 33
Martin Avatar answered Oct 25 '22 12:10

Martin


I've implemented custom HttpMessageHandler that tracks cookies.

It uses reflection to invoke the actual handler and just reads/sets Cookie headers.

class TestMessageHandler : HttpMessageHandler
{
    delegate Task<HttpResponseMessage> HandlerSendAsync(HttpRequestMessage message, CancellationToken token);

    private readonly HandlerSendAsync nextDelegate;
    private readonly CookieContainer cookies = new System.Net.CookieContainer();

    public TestMessageHandler(HttpMessageHandler next)
    {
        if(next == null) throw new ArgumentNullException(nameof(next));

        nextDelegate = (HandlerSendAsync)
                            next.GetType()
                                .GetTypeInfo()
                                .GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance)
                                .CreateDelegate(typeof(HandlerSendAsync), next);
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Add("Cookie", cookies.GetCookieHeader(request.RequestUri));

        var resp = await nextDelegate(request, cancellationToken).ConfigureAwait(false);

        if (resp.Headers.TryGetValues("Set-Cookie", out var newCookies))
        {
            foreach (var item in SetCookieHeaderValue.ParseList(newCookies.ToList()))
            {
                cookies.Add(request.RequestUri, new Cookie(item.Name, item.Value, item.Path));
            }
        }

        return resp;
    }
}

And then you create your HttpClient like this:

var httpClient = new HttpClient(
                     new TestMessageHandler(
                         server.CreateHandler()));

TestMessageHandler now takes care of tracking cookies.

like image 26
Oleh Nechytailo Avatar answered Oct 25 '22 12:10

Oleh Nechytailo