Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mock IHttpContextAccessor in Unit Tests

I have a method to get header value using IHttpContextAccessor

public class HeaderConfiguration : IHeaderConfiguration {     public HeaderConfiguration()     {      }      public string GetTenantId(IHttpContextAccessor httpContextAccessor)     {         return httpContextAccessor.HttpContext.Request.Headers["Tenant-ID"].ToString();     } } 

I am testing GetBookByBookId method

Let's say the method looks like this:

public class Book {     private readonly IHttpContextAccessor _httpContextAccessor;     private IHeaderConfiguration _headerConfiguration;     private string _tenantID;      public Book(IHeaderConfiguration headerConfiguration, IHttpContextAccessor httpContextAccessor){         var headerConfig = new HeaderConfiguration();         _httpContextAccessor = httpContextAccessor;         _tenantID = headerConfig.GetTenantId(_httpContextAccessor);     }      public Task<List<BookModel>> GetBookByBookId(string id){         //do something with the _tenantId         //...     } } 

Here's my unit test for GetBookByBookId method

[Fact] public void test_GetBookByBookId() {     //Arrange      //Mock IHttpContextAccessor     var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();      mockHttpContextAccessor.Setup(req => req.HttpContext.Request.Headers["Tenant-ID"].ToString()).Returns(It.IsAny<string>());     //Mock HeaderConfiguration     var mockHeaderConfiguration = new Mock<IHeaderConfiguration>();     mockHeaderConfiguration.Setup(x => x.GetTenantId(mockHttpContextAccessor.Object)).Returns(It.IsAny<string>());      var book = new Book( mockHttpContextAccessor.Object, mockHeaderConfiguration.Object);      var bookId = "100";      //Act     var result = book.GetBookByBookId(bookId);      //Assert     result.Result.Should().NotBeNull().And.         BeOfType<List<BookModel>>(); } 

But for this line:

mockHttpContextAccessor.Setup(req => req.HttpContext.Request.Headers["Tenant-ID"].ToString()).Returns(It.IsAny<string>()); 

It says

System.NotSupportedException: 'Type to mock must be an interface or an abstract or non-sealed class. '

I was wondering what's the proper way to mock IHttpContextAccessor with header value?

like image 269
superninja Avatar asked Jun 18 '18 20:06

superninja


People also ask

Should I use IHttpContextAccessor?

It's only necessary to use IHttpContextAccessor when you need access to the HttpContext inside a service.

What is IHttpContextAccessor?

ASP.NET Core applications access the HTTPContext through the IHttpContextAccessor interface. The HttpContextAccessor class implements it. You can use this class when you need to access HttpContext inside a service.

What is the use of mock in unit testing C#?

Mocking is a process used in unit testing when the unit being tested has external dependencies. The purpose of mocking is to isolate and focus on the code being tested and not on the behavior or state of external dependencies.


2 Answers

You can use the DefaultHttpContext as a backing for the IHttpContextAccessor.HttpContext. Saves you having to set-up too many things

Next you cannot use It.IsAny<string>() as a Returns result. They were meant to be used in the set up expressions alone.

Check the refactor

[Fact] public async Task test_GetBookByBookId() {     //Arrange      //Mock IHttpContextAccessor     var mockHttpContextAccessor = new Mock<IHttpContextAccessor>();     var context = new DefaultHttpContext();     var fakeTenantId = "abcd";     context.Request.Headers["Tenant-ID"] = fakeTenantId;     mockHttpContextAccessor.Setup(_ => _.HttpContext).Returns(context);     //Mock HeaderConfiguration     var mockHeaderConfiguration = new Mock<IHeaderConfiguration>();     mockHeaderConfiguration         .Setup(_ => _.GetTenantId(It.IsAny<IHttpContextAccessor>()))         .Returns(fakeTenantId);      var book = new Book(mockHttpContextAccessor.Object, mockHeaderConfiguration.Object);      var bookId = "100";      //Act     var result = await book.GetBookByBookId(bookId);      //Assert     result.Should().NotBeNull().And.         BeOfType<List<BookModel>>(); } 

There may also be an issue with the Class Under Test as it is manually initializing the HeaderConfiguration when it should actually be explicitly injected.

public Book(IHeaderConfiguration headerConfiguration, IHttpContextAccessor httpContextAccessor) {     _httpContextAccessor = httpContextAccessor;     _tenantID = headerConfiguration.GetTenantId(_httpContextAccessor); } 
like image 171
Nkosi Avatar answered Sep 28 '22 02:09

Nkosi


In my scenario I had to mock IHttpContextAccessor and access the inner request url bits.
I'm sharing it here because I spent a decent amount of time figuring this out and hopefully it'll help someone.

readonly Mock<IHttpContextAccessor> _HttpContextAccessor =    new Mock<IHttpContextAccessor>(MockBehavior.Strict);  void SetupHttpContextAccessorWithUrl(string currentUrl) {   var httpContext = new DefaultHttpContext();   setRequestUrl(httpContext.Request, currentUrl);    _HttpContextAccessor     .SetupGet(accessor => accessor.HttpContext)     .Returns(httpContext);    static void setRequestUrl(HttpRequest httpRequest, string url)   {     UriHelper       .FromAbsolute(url, out var scheme, out var host, out var path, out var query,          fragment: out var _);      httpRequest.Scheme = scheme;     httpRequest.Host = host;     httpRequest.Path = path;     httpRequest.QueryString = query;   } } 
like image 24
Shimmy Weitzhandler Avatar answered Sep 28 '22 00:09

Shimmy Weitzhandler