6

I am trying to unit test code that uses SearchClient.SearchAsync() method. I am using AutoFixture.AutoMoq nuget package.

Here is what I tried:

mockSearchClient.Setup(msc => msc.SearchAsync<MyModel>(
        It.IsAny<string>(),
        It.IsAny<SearchOptions>(),
        It.IsAny<CancellationToken>()
    )).Returns(Task.FromResult(<<PROBLEM HERE>>));

The problem lies in the parameter .Returns(Task.FromResult(<<PROBLEM HERE>>)) part. It expects a concrete object that is returned from the .SearchAsync() method. According to docs and autocomplete, the method returns Azure.Response which is an abstract class. So, I cannot new it up. In actuality, the method returns a descendant class Azure.ValueResponse, which isn't abstract, but is internal to Azure SDK, so also impossible to new up.

So how does one mock the SearchClient.SearchAsync?

P.S. Using Azure.Search.Documents, v11.1.1.0

nsturdivant
  • 273
  • 2
  • 10
AngryHacker
  • 59,598
  • 102
  • 325
  • 594
  • And you can't create your own fake derived from the abstract response class? – Bertrand Le Roy Oct 01 '20 at 20:17
  • @BertrandLeRoy The mocking frameworks generally require you to provide an instance. So I tried creating my own class that inherited from Azure.Response (`class foo: Azure.Response {}`), but that didn't work either. If you have a different way to do it, I'd love to try it. – AngryHacker Oct 01 '20 at 21:02

1 Answers1

11

See https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/core/Azure.Core/README.md#mocking for information. Basically, you can use Response.FromValue along with the SearchModelFactory (a pattern we follow with all our Azure.* client SDKs for models that can't be fully mocked with a constructor and/or settable properties) to create a mock like so (using Moq, since I'm unfamiliar with AutoMoq, but should be similar):

var responseMock = new Mock<Response>();

var clientMock = new Mock<SearchClient>(() => new SearchClient(new Uri("https://localhost"), "index", new AzureKeyCredential("key")));
clientMock.SetupGet(x => x.IndexName).Returns("index");
clientMock.Setup(x => x.SearchAsync<Hotel>(
        It.IsAny<string>(),
        It.IsAny<SearchOptions>(),
        It.IsAny<CancellationToken>()
    ))
    .Returns(
        Task.FromResult(
            Response.FromValue(
                SearchModelFactory.SearchResults(new[]
                    {
                        SearchModelFactory.SearchResult(new Hotel("1", "One"), 0.9, null),
                        SearchModelFactory.SearchResult(new Hotel("2", "Two"), 0.8, null),
                    },
                    100,
                    null,
                    null,
                    responseMock.Object),
                responseMock.Object)));

var results = await clientMock.Object.SearchAsync<Hotel>("test").ConfigureAwait(false);
var hotels = results.Value;

Assert.Equal(2, hotels.GetResults().Count());
Assert.Equal(100, hotels.TotalCount);
Heath
  • 2,986
  • 18
  • 21
  • Just wanted to add that the delegate form used in `new Mock` is only necessary because of a bug that has since been fixed: https://github.com/Azure/azure-sdk-for-net/pull/15671. Normally, you can just use the construct that takes parameters passed through to the client e.g. `SearchClient`. – Heath Oct 05 '20 at 23:20
  • Thanks @heath, this helped me find `TableModelFactory` to mock the `TableServiceClient` response model – Sam Jones Jun 09 '22 at 11:43