Ganesha
Ganesha

Reputation: 1531

Unit Test - Mock - Issue with Overriddable - Extension Method

I'm trying to write unit test for the following Azure search method:

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

Unit Test Code 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
}

I get the following error:

Message: System.NotSupportedException : Unsupported expression: ... => ....Index(It.IsAny>(), It.IsAny()) Extension methods (here: DocumentsOperationsExtensions.Index) may not be used in setup / verification expressions.

Unit Test Code 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
}

I get the following error:

Message: System.NullReferenceException : Object reference not set to an instance of an object.
    at Microsoft.Azure.Search.DocumentsOperationsExtensions.IndexAsync[T](IDocumentsOperations operations, IndexBatch^1 batch, SearchRequestOptions searchRequestOptions, CancellationToken cancellationToken)
    at Microsoft.Azure.Search.DocumentsOperationsExtensions.Index[T](IDocumentsOperations operations, IndexBatch^1 batch, SearchRequestOptions searchRequestOptions)

How do I test the upload functionality?

Upvotes: 3

Views: 1508

Answers (2)

Nkosi
Nkosi

Reputation: 247451

You are basically trying to test the functionality of 3rd party dependencies. As much as those dependencies have abstractions that can be mocked, they require an unnecessary amount of setup to isolate them for unit testing, which for me is a code smell.

I suggest abstracting out that 3rd party dependency

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

Try to avoid tightly coupling to static concerns as it makes unit testing in isolation difficult.

The service definition can look like

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

with a simple implementation that wraps the external dependency

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

Stop stressing over trying to test code you have no control of. Focus instead on your logic that you can control.

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

Upvotes: 5

Sagar Agrawal
Sagar Agrawal

Reputation: 14

As per documentation,

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

There is only one method Index and it has two paramters.

In UploadContent method, the Index method only one argument is passed. Is there some version difference in the two projects?

And...

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

In the setup, the return should be a concrete instance rather than doing It.IsAny<IDocumentsOperations>().

Upvotes: 0

Related Questions