Skip to content

How test API with Snapshot Tests?

Posted on:July 5, 2022 at 02:05 AM

Introduction

Testing is a crucial aspect of any software project. Tests are necessary to improve and evolve the project. However, with great power comes great responsibility, and tests should be easy to maintain; otherwise, our development process will slow down.

In this article, we will focus on integration testing, particularly for a REST API endpoint that returns a paginated list.

Classic Assertions Problem

There are several ways to assert the results of an API test, such as hard-coding expected items and asserting one item or hard-coding all items and asserting all items. However, when the API is nested and the contract changes, you will need to rewrite your assertions, and you may need to modify several places.

How to make assertions easier to maintain❓

I would like to highlight the snapshot testing approach (also known as approval testing).

Snapshot Testing

Approval testing is a different approach to asserting results. In snapshot testing, we execute the test for the first time and record the output as a snapshot. We then compare subsequent test results against this snapshot to ensure that the API is still behaving as expected. This approach makes it easier to maintain tests as the contract changes since we only need to update the snapshot instead of multiple assertions.

Let’s skip the theory and proceed step by step.

[UsesVerify]
public class ProductsControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
    [Theory]
    [ClassData(typeof(GetProductTestCases))]
    public async Task GetProducts_ShouldReturnListOfProducts(IReadOnlyCollection<Product> products)
    {
        // Arrange
        await CreateProduct(products);

        // Act
        var response = await _client.GetAsync(ProductsApi);

        // Assert
        response.EnsureSuccessStatusCode();
        var result = await response.Content.ReadFromJsonAsync<List<Product>>();
        Verify(result);
    }
}

Snapshot Testing with [UseVerify] Attribute

In these integration tests, a notable feature is the use of the [UseVerify] attribute in the classes. Instead of employing traditional assertions, the test results are passed to a function called Verify(). Alt text

To enhance the workflow with snapshot testing, the Rider Plugin (Verify Support) is utilized. As illustrated in the diagram, when a test is executed for the first time, the Verify function is invoked, opening a git diff window. Alt text

Reviewing and Approving Test Results

Upon execution of the Verify function, the test results are reviewed. Based on the outcomes, there is the option to either approve them as expected or reject them, initiating an attempt to fix the test. After making necessary corrections, the results can be reviewed and approved again.

Following approval, a text file containing the results is generated, known as a snapshot. For instance, if a change in functionality causes the method to return an empty list instead of a filled one, the test will fail.

Contract Changes and Diffs

When adding a property to the contract, a diff will be opened. Developers can review the changes and approve them if they appear correct. Notably, despite making changes to the functionality, the modification of tests is minimal.

Trade-off Analysis

Pros

Cons

As with most techniques, there are trade-offs. While snapshot testing offers advantages, it is essential to be aware of its limitations.