2015-05-31 16 views
10

Powiedzmy, że ustawiam wartość na kontekście http w moim oprogramowaniu pośredniczącym. Na przykład HttpContext.User.Jak uzyskać dostęp do HttpContext w teście jednostki w ASP.NET 5/MVC 6

W jaki sposób przetestować kontekst http w moim teście jednostki. Oto przykład tego, co próbuję zrobić

Middleware

public class MyAuthMiddleware 
{ 
    private readonly RequestDelegate _next; 

    public MyAuthMiddleware(RequestDelegate next) 
    { 
     _next = next; 
    } 

    public async Task Invoke(HttpContext context) 
    { 
     context.User = SetUser(); 
     await next(context); 
    } 
} 

test

[Fact] 
public async Task UserShouldBeAuthenticated() 
{ 
    var server = TestServer.Create((app) => 
    { 
     app.UseMiddleware<MyAuthMiddleware>(); 
    }); 

    using(server) 
    { 
     var response = await server.CreateClient().GetAsync("/"); 
     // After calling the middleware I want to assert that 
     // the user in the HttpContext was set correctly 
     // but how can I access the HttpContext here? 
    } 
} 

Odpowiedz

13

Poniżej przedstawiono dwa podejścia można użyć:

// Directly test the middleware itself without setting up the pipeline 
[Fact] 
public async Task Approach1() 
{ 
    // Arrange 
    var httpContext = new DefaultHttpContext(); 
    var authMiddleware = new MyAuthMiddleware(next: (innerHttpContext) => Task.FromResult(0)); 

    // Act 
    await authMiddleware.Invoke(httpContext); 

    // Assert 
    // Note that the User property on DefaultHttpContext is never null and so do 
    // specific checks for the contents of the principal (ex: claims) 
    Assert.NotNull(httpContext.User); 
    var claims = httpContext.User.Claims; 
    //todo: verify the claims 
} 

[Fact] 
public async Task Approach2() 
{ 
    // Arrange 
    var server = TestServer.Create((app) => 
    { 
     app.UseMiddleware<MyAuthMiddleware>(); 

     app.Run(async (httpContext) => 
     { 
      if(httpContext.User != null) 
      { 
       await httpContext.Response.WriteAsync("Claims: " 
        + string.Join(
         ",", 
         httpContext.User.Claims.Select(claim => string.Format("{0}:{1}", claim.Type, claim.Value)))); 
      } 
     }); 
    }); 

    using (server) 
    { 
     // Act 
     var response = await server.CreateClient().GetAsync("/"); 

     // Assert 
     var actual = await response.Content.ReadAsStringAsync(); 
     Assert.Equal("Claims: ClaimType1:ClaimType1-value", actual); 
    } 
} 
+0

Widzę, że w drugim podejściu użyłeś oprogramowania pośredniego do napisania odpowiedzi, która jest naprawdę sprytna. Mówiąc to, nie ma innego sposobu na uzyskanie kontekstu http w drugim podejściu, powiedzmy IHttpContextAccessor? –

+1

@SulAga: Powiedziałbym, że drugie podejście to tak naprawdę test integracyjny, a nie test jednostkowy. Jeśli chodzi o twoje pytanie, kontekst jest dostarczany bezpośrednio do metody Invoke oprogramowania pośredniego, więc nie musisz używać IHttpContextAccessor. –

+0

Używam asp.net rtm.Kiedy próbuję użyć podejścia 1, otrzymuję zerowy wyjątek odwołania gdzieś głęboko w aspnet.corelib. jakieś pomysły? – pthalacker

1

spojrzeć na tym stanowisku:

Setting HttpContext.Current.Session in a unit test

Myślę, że to, czego potrzebujesz, to jest to.

public static HttpContext FakeHttpContext(string url) 
{ 
    var uri = new Uri(url); 
    var httpRequest = new HttpRequest(string.Empty, uri.ToString(), 
             uri.Query.TrimStart('?')); 
    var stringWriter = new StringWriter(); 
    var httpResponse = new HttpResponse(stringWriter); 
    var httpContext = new HttpContext(httpRequest, httpResponse); 

    var sessionContainer = new HttpSessionStateContainer("id", 
            new SessionStateItemCollection(), 
            new HttpStaticObjectsCollection(), 
            10, true, HttpCookieMode.AutoDetect, 
            SessionStateMode.InProc, false); 

    SessionStateUtility.AddHttpSessionStateToContext(
             httpContext, sessionContainer); 

    return httpContext; 
} 

Wtedy można go używać jak:

request.SetupGet(req => req.Headers).Returns(new NameValueCollection()); 
HttpContextFactory.Current.Request.Headers.Add(key, value); 
+2

Nie dotyczy to programu ASP.Net 5/MVC 6, który ma [nowy model HttpContext) (http://gunnarpeipman.com/2014/07/asp-net-5-new-httpcontext/) –

2

Byłoby lepiej, jeśli jednostka przetestować klasy middleware w oderwaniu od reszta twojego kodu.

Od HttpContext klasa jest klasą abstrakcyjną, można użyć ramy szyderczy jak Moq (dodając "Moq": "4.2.1502.911", jako zależność do pliku project.json), aby sprawdzić, czy nieruchomość użytkownik został ustawiony.

Na przykład można napisać następujący test, który weryfikuje middleware Invoke funkcja jest ustawienie właściwości użytkownika w HTTPContext i wywołanie kolejnej warstwy pośredniej:

[Fact] 
public void MyAuthMiddleware_SetsUserAndCallsNextDelegate() 
{ 
    //Arrange 
    var httpContextMock = new Mock<HttpContext>() 
      .SetupAllProperties(); 
    var delegateMock = new Mock<RequestDelegate>(); 
    var sut = new MyAuthMiddleware(delegateMock.Object); 

    //Act 
    sut.Invoke(httpContextMock.Object).Wait(); 

    //Assert 
    httpContextMock.VerifySet(c => c.User = It.IsAny<ClaimsPrincipal>(), Times.Once); 
    delegateMock.Verify(next => next(httpContextMock.Object), Times.Once); 
} 

Następnie można napisać dodatkowe testy do weryfikacji użytkownik ma oczekiwane wartości, ponieważ będziesz w stanie uzyskać setted obiekt użytkownika z httpContextMock.Object.User:

Assert.NotNull(httpContextMock.Object.User); 
//additional validation, like user claims, id, name, roles 
+0

Po prostu dla FYI ... używanie Moq jest również w porządku, ale obecnie nie działa na core clr. –

+0

Dobrze wiedzieć! Więc jeśli chcesz celować w core clr, musisz albo użyć DefaultHttpContext, jak w twoim przykładzie, albo utworzyć manualne klasy makr rozciągające HttpContext. –

+0

Tak, chociaż preferuje użycie DefaultHttpContext, ponieważ został zaprojektowany z myślą o testowalności jednostki. –

7

wersja RC1 asp.net 5/MVC6 sprawia, że ​​można ustawić ręcznie w Un HttpContext to testy, które są niesamowite!

 DemoController demoController = new DemoController(); 
     demoController.ActionContext = new ActionContext(); 
     demoController.ActionContext.HttpContext = new DefaultHttpContext(); 
     demoController.HttpContext.Session = new DummySession(); 

klasa DefaultHttpContext jest zapewniana przez platformę. DummySession może być prostą klasą, która implementuje klasę ISession. To upraszcza wiele rzeczy, ponieważ nie jest już konieczne kpiny.

+1

'HttpContext' został przemianowany na' ControllerContext'. Ale twoja odpowiedź była przydatna do osiągnięcia czegoś związanego. – thoean

Powiązane problemy