单元测试-模拟-可覆盖问题-扩展方法

时间:2019-11-02 15:21:58

标签: c# azure unit-testing mocking moq

我正在尝试为以下Azure搜索方法编写单元测试:

public async Task Write(ISearchIndexClient indexClient, Search search)
{
    await UploadContents(indexClient, search.SearchContents);
}

private static async Task UploadContents(ISearchIndexClient indexClient, IReadOnlyCollection<dynamic> searchContents) => await Task.Run(() => indexClient.Documents.Index(IndexBatch.Upload(searchContents)));

单元测试代码1:

public async Task Write_Success()
{
    var searchIndexClientMock = new Mock<ISearchIndexClient>();
    searchIndexClientMock
        .Setup(x => x.Documents.Index(It.IsAny<IndexBatch<Document>>(), It.IsAny<SearchRequestOptions>()))
        .Returns(It.IsAny<DocumentIndexResult>()).Callback(() => IndexBatch.Upload(It.IsAny<IEnumerable<Document>>()));

    var pushFunction = new SearchIndexWriter();

    Search search = new Search();

    await pushFunction.Write(searchIndexClientMock.Object, search);

    //Assert, Verify checks
}

我收到以下错误:

  

消息:System.NotSupportedException:不支持的表达式:... => .... Index(It.IsAny>(),It.IsAny())   扩展方法(此处为:DocumentsOperationsExtensions.Index)不得在设置/验证表达式中使用。

单元测试代码2:

public async Task Write_Success()
{
    var searchIndexClientMock = new Mock<ISearchIndexClient>();
    searchIndexClientMock
        .SetupGet(x => x.Documents).Returns(It.IsAny<IDocumentsOperations>());

    var pushFunction = new SearchIndexWriter();

    var search = new Search()
    {
        SearchContents = new List<dynamic>(),
    };

    await pushFunction.Write(searchIndexClientMock.Object, search);

    //Verify, Assert logic
}

我收到以下错误:

  

消息:System.NullReferenceException:对象引用未设置为对象的实例。   
Microsoft.Azure.Search.DocumentsOperationsExtensions.IndexAsync [T](IDocumentsOperations操作,IndexBatch ^ 1批处理,SearchRequestOptions searchRequestOptions,CancellationToken cancelleToken)   
Microsoft.Azure.Search.DocumentsOperationsExtensions.Index [T](IDocumentsOperations操作,IndexBatch ^ 1批处理,SearchRequestOptions searchRequestOptions)

如何测试上传功能?

2 个答案:

答案 0 :(得分:4)

您基本上是在尝试测试第三方依赖项的功能。尽管这些依赖项具有可以模拟的抽象,但是它们需要不必要的设置量来隔离它们以进行单元测试,这对我来说是代码的味道。

我建议抽象出第三方依赖性

private readonly ISearchService service;

//...assuming service injected
public SearchIndexWriter(ISearchService service) {
    this.service = service;
}

public Task Write(ISearchIndexClient indexClient, Search search) {
    return service.UploadContents(indexClient, search.SearchContents);
}

请尝试避免与静态问题紧密联系,因为这会使隔离中的单元测试变得困难。

服务定义看起来像

public interface ISearchService {
    Task UploadContents(ISearchIndexClient indexClient, IReadOnlyCollection<dynamic> searchContents);
}

具有包装外部依赖项的简单实现

public class SearchService : ISearchService  {
    private Task UploadContents(ISearchIndexClient indexClient, IReadOnlyCollection<dynamic> searchContents) 
        =>  indexClient.Documents.IndexAsync(IndexBatch.Upload(searchContents));
}

不要再试图测试您无法控制的代码了。而是专注于可以控制的逻辑。

public async Task Write_Success() {
    //Arrange
    var serviceMock = new Mock<ISearchService>();
    serviceMock
        .Setup(_ => _.UploadContents(It.IsAny<ISearchIndexClient>(), It.IsAny<IReadOnlyCollection<It.AnyType>>())
        .ReturnsAsync(new object());

    var searchIndexClientMock = Mock.Of<ISearchIndexClient>();

    var pushFunction = new SearchIndexWriter(serviceMock.Object);

    Search search = new Search();

    //Act
    await pushFunction.Write(searchIndexClientMock.Object, search);

    //Assert, Verify checks
    //...
}

答案 1 :(得分:0)

根据文档,

https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.search.idocumentsoperations?view=azure-dotnet

只有一种方法Index,它有两个参数。

UploadContent方法中,Index方法仅传递一个参数。这两个项目有版本差异吗?

然后...

searchIndexClientMock
        .SetupGet(x => x.Documents).Returns(It.IsAny<IDocumentsOperations>());

在设置中,返回应该是一个具体实例,而不是执行It.IsAny<IDocumentsOperations>()