【问题标题】:Mock IHttpContextAccessor in Unit Tests在单元测试中模拟 IHttpContextAccessor
【发布时间】:2018-11-27 17:49:10
【问题描述】:

我有一种使用IHttpContextAccessor获取标头值的方法

public class HeaderConfiguration : IHeaderConfiguration
{
    public HeaderConfiguration()
    {

    }

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

我正在测试GetBookByBookId 方法

假设方法如下所示:

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
        //...
    }
}

这是我对GetBookByBookId 方法的单元测试

[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>>();
}

但是对于这一行:

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

上面写着

System.NotSupportedException: '要模拟的类型必须是接口或抽象或非密封类。 '

我想知道用标头值模拟 IHttpContextAccessor 的正确方法是什么?

【问题讨论】:

    标签: c# unit-testing asp.net-core moq asp.net-core-webapi


    【解决方案1】:

    您可以使用DefaultHttpContext 作为IHttpContextAccessor.HttpContext 的支持。省去你设置太多东西的麻烦

    接下来你不能使用It.IsAny&lt;string&gt;() 作为Returns 结果。它们只能用于设置表达式。

    检查重构

    [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>>();
    }
    

    被测类也可能存在问题,因为它在实际应该显式注入时手动初始化HeaderConfiguration

    public Book(IHeaderConfiguration headerConfiguration, IHttpContextAccessor httpContextAccessor) {
        _httpContextAccessor = httpContextAccessor;
        _tenantID = headerConfiguration.GetTenantId(_httpContextAccessor);
    }
    

    【讨论】:

      【解决方案2】:

      在我的场景中,我必须模拟 IHttpContextAccessor 并访问内部请求 url 位。
      我在这里分享它是因为我花了相当多的时间来解决这个问题,希望它会对某人有所帮助。

      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;
        }
      }
      

      【讨论】:

      • _HttpContextAccessor.SetupGet(accessor =&gt; accessor.HttpContext).Returns(httpContext);
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-13
      • 2017-02-09
      • 1970-01-01
      • 2015-05-04
      • 2016-11-14
      相关资源
      最近更新 更多