web-dev-qa-db-ja.com

ユニットテストでのIHttpContextAccessorのモック

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をモックする適切な方法は何ですか?

15
superninja

DefaultHttpContextを_IHttpContextAccessor.HttpContext_のバッキングとして使用できます。あまりにも多くのものをセットアップする必要がなくなります

次に、Returnsの結果としてIt.IsAny<string>()を使用できません。それらは、セットアップ式のみで使用されることを意図していました。

リファクタリングを確認する

_[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);
}
_
23
Nkosi