0

I've run into a problem while testing an Azure Function which runs on top of Azure Tables: How can I fake the result of a table query which returns an AsyncPageable?

Here's the code under test...

public async Task<Project?> GetProjectByIdAsync(Guid projectId)
{
    const int maxResultsPerPage = 1;
    IEnumerable<string>? selectProps = null;

    // _projectsTableService is an ITableService.
    var tableClient = _projectsTableService.GetTableClient();

    var results = tableClient.QueryAsync<Project>(project => project.Id.Equals(projectId),
                                                  maxResultsPerPage,
                                                  selectProps,
                                                  CancellationToken.None);

    await foreach (var result in results)
    {
        return result;
    }

    return null;
}

And here's my work-in-progress test method...

[TestInitialize]
public void TestInit()
{
    _project = new Project { Id = Guid.NewGuid() };
    _tableClient = A.Fake<TableClient>();
    _tableService = A.Fake<ITableService>();
    _tableServiceFactory = A.Fake<ITableServiceFactory>();

    A.CallTo(() => _tableService.GetTableClient()).Returns(_tableClient);
    A.CallTo(() => _tableServiceFactory.Create(TableName)).Returns(_tableService);

    _testee = new ProjectsTableService(_tableServiceFactory);
}

[TestMethod]
public async Task GetProjectByIdAsync_WhenMatchingProjectFound_ReturnsProject()
{
    var projectId = Guid.NewGuid();
    var project = new Project { Id = projectId };
    var projectsResult = new List<Project> { _project };
    var asyncPageable = A.Fake<AsyncPageable<Project>>();
    var enumerator = projectsResult.ToAsyncEnumerable().GetAsyncEnumerator();

    A.CallTo(() => asyncPageable.GetAsyncEnumerator(A<CancellationToken>._)).Returns(enumerator);

    A.CallTo(() => _tableClient.QueryAsync(A<Expression<Func<Project, bool>>>._,
                                           A<int>._,
                                           A<IEnumerable<string>>._,
                                           A<CancellationToken>._)).Returns(asyncPageable);

    var result = await _testee.GetProjectByIdAsync(_project.Id);

    result.Should().NotBeNull();
    result.Id.Should().Be(projectId);
}

When I step through this test the results AsyncPageable<Project> contains no results.


UPDATE
Here's a second attempt based on this example...

[TestMethod]
public async Task GetProjectByIdAsync_WhenMatchingProjectFound_ReturnsProject2()
{
    var projectId = Guid.NewGuid();
    var project = new Project { Id = projectId };
    var projects = new List<Project> { _project };
    var page = Page<Project>.FromValues(projects,
                                        continuationToken: null,
                                        response: A.Fake<Response>());
    var pages = AsyncPageable<Project>.FromPages(new[] { page });

    A.CallTo(() => _tableClient.QueryAsync(A<Expression<Func<Project, bool>>>._,
                                           A<int>._,
                                           A<IEnumerable<string>>._,
                                           A<CancellationToken>._)).Returns(pages);

    var result = await _testee.GetProjectByIdAsync(_project.Id);

    result.Should().NotBeNull();
    result.Id.Should().Be(projectId);
}

Can anyone help me to fake this AsyncPageable result?

awj
  • 7,482
  • 10
  • 66
  • 120
  • 1
    I'm going to need a little more to be able to help. I see you make a fake `asyncPageable`, which `QueryAsynch` should return, but I don't see how `_tableClient` is supplied to `_testee`. (I don't know what `_projectsTableService` is, or how it's supposed to know about `_tableClient`.) When you debug into `GetProjectByIdAsync` is the `tableClient` the same object as `_tableClient`? Does it show as a Fake? Your signature looks okay to me, but are you configuring one overoad of `QueryAsync` and calling another? Inside `GetProjectByIdAsync` is `results` a Fake type? – Blair Conrad Jun 05 '23 at 00:15
  • 1
    Have you seen [this QA](https://stackoverflow.com/questions/75433821/how-to-create-an-azure-asyncpageable-for-mocking/75433990#75433990)? – Peter Bons Jun 05 '23 at 06:48
  • @PeterBons I _hadn't_ seen that QA, no - thanks for the link. I don't think I had found it because I was searching for "fake" instead of "mock". However, despite looking promising, when I step through the code I still find that the returned `AsyncPageable` contains nothing. I've added the resultant test method to my OP. – awj Jun 05 '23 at 17:41

1 Answers1

1

I found out the cause of my problem: As @BlairConrad alluded to in his comment, it was in my initial set-up of the dependencies...

A.CallTo(() => _tableServiceFactory.Create(TableName)).Returns(_tableService);

The TableName member differs between my test class and the implementation. I'm still not sure why I was getting any faked objects, but that's a secondary question.

So I now have a working test using code found in the post lined by @PeterBons', which is shown in the update in the OP.

awj
  • 7,482
  • 10
  • 66
  • 120
  • "I'm still not sure why I was getting any faked objects" because `_tableServiceFactory` was a Fake. You call `Create` on it, and it'll try to create something for you. See [Default fake behavior](https://fakeiteasy.github.io/docs/7.4.0/default-fake-behavior/). Glad you got a solution. I was a little slow coming back here. – Blair Conrad Jun 06 '23 at 11:13