DEV Community

Andrew Chaa
Andrew Chaa

Posted on • Edited on • Originally published at andrewchaa.me.uk

8 1

Mocking Cosmos Container method for unit testing

In my unit/component tests, I often assert that all the correct values are passed into Cosmos Container from the api request payload. By asserting that all the parameters are correctly passed, I can make sure the endpoint is doing its job correctly.

private readonly TestApplicationFactory<Startup> _factory;
private readonly ITestOutputHelper _output;
private readonly HttpClient _client;
private readonly Fixture _fixture;

public CreateTransactionTests(TestApplicationFactory<Startup> factory, 
    ITestOutputHelper output)
{
    _factory = factory;
    _output = output;
    _client = factory.CreateClient();
    _fixture = new Fixture();
}

[Fact]
public async Task Should_store_correct_transaction_details_in_the_database_and_return_200_Ok()
{
    // Arrange
    _factory.MockContainer.Reset();

    var transactionId = Guid.NewGuid();
    var request = _fixture.Build<CreateTransactionRequest>()
        .With(x => x.Currency, "EUR")
        .Create();

    TransactionData transactionData = null;

    var mockItemResponse = new Mock<ItemResponse<TransactionData>>();

    mockItemResponse.Setup(x => x.StatusCode)
        .Returns(HttpStatusCode.OK);

    _factory.MockContainer.Setup(x => x.CreateItemAsync(It.IsAny<TransactionData>(),
            It.IsAny<PartitionKey>(),
            It.IsAny<ItemRequestOptions>(),
            default(CancellationToken)))
        .ReturnsAsync(mockItemResponse.Object)                
        .Callback<TransactionData, PartitionKey?, RequestOptions, CancellationToken>(
            (t, p, r, c) => transactionData = t);

    // Act
    var response = await _client.PutAsync($"/v1/transactions/{transactionId}", request.ToStringContent());

    // Assert
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    Assert.Equal(request.AccountId, transactionData.AccountId);
    Assert.Equal(request.Amount, transactionData.Amount);
    Assert.Equal(request.Currency, transactionData.Currency);
    ...
}

Enter fullscreen mode Exit fullscreen mode

xUnit's IClassFixture

IClassFixture lets you use a shared class to use in your tests. I put all of my my infrastructure plumbing code there.

public class TestApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    public Mock<Container> MockContainer;

    public TestApplicationFactory() { }

    protected override IWebHostBuilder CreateWebHostBuilder() => new WebHostBuilder();

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        MockContainer = new Mock<Container>();

        builder
            .UseKestrel()
            .UseEnvironment(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Development")
            .UseContentRoot(Directory.GetCurrentDirectory())
            .ConfigureAppConfiguration((context, configBuilder) =>
            {
                configBuilder.SetBasePath(context.HostingEnvironment.ContentRootPath);
                configBuilder.AddJsonFile("appsettings.json", true);
                configBuilder.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true);
            })
            .UseStartup<Startup>();

        builder.ConfigureTestServices(services =>
        {
            services.AddSingleton<ITransactionRepository>(x => 
                new TransactionRepository(MockContainer.Object, new NullLogger<TransactionRepository>()));
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

I have public MockContainer that mocks Cosmos Container.

AutoFixture

AutoFixture is very handy to populate a request object.

var request = _fixture.Build<CreateTransactionRequest>()
    .With(x => x.Currency, "EUR")
    .Create();
Enter fullscreen mode Exit fullscreen mode

Set up Mock Cosmos Container

So that I can verify what parameters the Container receives. I use moq's Callback. I prefer it over Verify becasue I can assert each value per each line.

var mockItemResponse = new Mock<ItemResponse<TransactionData>>();

mockItemResponse.Setup(x => x.StatusCode)
    .Returns(HttpStatusCode.OK);

_factory.MockContainer.Setup(x => x.CreateItemAsync(It.IsAny<TransactionData>(),
        It.IsAny<PartitionKey>(),
        It.IsAny<ItemRequestOptions>(),
        default(CancellationToken)))
    .ReturnsAsync(mockItemResponse.Object)                
    .Callback<TransactionData, PartitionKey?, RequestOptions, CancellationToken>(
        (t, p, r, c) => transactionData = t);
Enter fullscreen mode Exit fullscreen mode

One thing to note is I used Mock<ItemResponse<TransactionData>>(). It's becasue ItemResponse has Internal constructor and I canot new up the class. So I used mock instead.

Assert

var response = await _client.PutAsync($"/v1/transactions/{transactionId}", request.ToStringContent());

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(request.AccountId, transactionData.AccountId);
Assert.Equal(request.Amount, transactionData.Amount);
Assert.Equal(request.Currency, transactionData.Currency);
...
Enter fullscreen mode Exit fullscreen mode

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (2)

Collapse
 
kkrishna_gopfs profile image
Kalyan Krishna

Thanks, saved me a few days of work :)

Collapse
 
mariomeyrelles profile image
Mario Meyrelles

Hello Andrew,

Have you been able to mock the ReadItemAsync also? Would be willing to share?

Kind Regards,
Mário

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️