在单元测试中模拟IHttpContextAccessor

70

我有一种方法可以使用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

3个回答

107

您可以使用DefaultHttpContext作为IHttpContextAccessor.HttpContext的支持。这样可以避免设置太多东西。

接下来,您不能将It.IsAny<string>()用作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);
}

你可以请进一步说明一下关于 HeaderConfiguration 的手动初始化的意思吗?我看到他手动创建了一个模拟对象并将其传递给 Book 类,就像我预期的那样。有什么我漏掉的吗? - Dave Black
@DaveBlack 如果您查看原始的 Book 构造函数,您会发现他们手动创建了一个 HeaderConfiguration 实例,并没有使用注入的 IHeaderConfiguration。这就是我所指的。 - Nkosi
啊,是的。我忘记了。 - Dave Black

6

在我的场景中,我需要模拟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 => accessor.HttpContext).Returns(httpContext); - Shimmy Weitzhandler

1
如果您正在使用出色的NSubstitute包进行NUnit测试,您可以这样做...
        var mockHttpAccessor = Substitute.For<IHttpContextAccessor>();
        var context = new DefaultHttpContext
        {
            Connection =
            {
                Id = Guid.NewGuid().ToString()
            }
        };
        
        mockHttpAccessor.HttpContext.Returns(context);
        
        // usage...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接