Testing a Spring application with MongoDB aggregations
Tags: mongodb, testing, java, spring
Published: 2024-01-07 20:20:47


Currently, I am working on an application that involves various MongoDB aggregations to calculate sums, averages, among many other things. 

I am using MongoDB's standard classes to implement these aggregations, rather than Spring Data, mainly to leverage MongoDB Compass's functionality to generate aggregation code that can be directly executed within the tool. The figure below illustrates how this can be done:

![Exporting MongoDB Aggregation to Language](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c6sl0m6f0e3il3kcbbzs.png)

The challenge now is how to test these aggregations. It would be possible to mock all the MongoDB classes and test the Java code. However, these tests may not be very useful since the complexity lies mainly in the aggregations, not in the code itself.

To address this, I remembered that some time ago, I used an embedded Postgres in a Spring application and decided to verify if it was possible to do the same with MongoDB. I found the **de.flapdoodle.embed.mongo.spring30x** library, which made this task possible. In this article, I will show a simple example of how to implement testing for a MongoDB aggregation using an embedded database instance.

The complete application code is available on GitHub:

[GitHub Repository](https://github.com/ezambomsantana/mongodb-testing)

I developed a very simple application that has three routes: one for creating users, another for listing users, and a third one that performs an aggregation on users, grouping them by city and calculating the average salary and age of the users.

The challenge is testing the method that performs the aggregation; it utilizes MongoDB access classes such as AggregateIterable and MongoDatabase. The code snippet below illustrates the implementation of this code:

```java
@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    private final MongoClient mongoClient;

    // other methods to save and list users

    public List<CityAggregationDTO> getAvg() {
        var database = mongoClient.getDatabase("test");
        var collection = database.getCollection("user");
        var result = collection.aggregate(Arrays.asList(new Document("$group",
                new Document("_id", "$city")
                        .append("avgAge",
                                new Document("$avg", "$age"))
                        .append("avgSalary",
                                new Document("$avg", "$salary")))));

        List<CityAggregationDTO> response = new ArrayList<>();
        for (Document doc : result) {
            var cityAggregationDTO = new CityAggregationDTO();
            cityAggregationDTO.setCity(doc.getString("_id"));
            cityAggregationDTO.setAvgSalary(doc.getDouble("avgSalary"));
            cityAggregationDTO.setAvgAge(doc.getDouble("avgAge"));
            response.add(cityAggregationDTO);
        }
        return response;
    }

}
```

The above code performs the aggregation by grouping users based on the city they live in and then calculating the average salary and age per city. This aggregation returns a list of results, with each object representing a different city.

Let's test this code now. To do that, the first step is to add the 'de.flapdoodle.embed.mongo.spring30x' library to the pom.xml file.

```xml
<dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo.spring30x</artifactId>
    <version>4.6.1</version>
    <scope>test</scope>
</dependency>
```

We need to add an entry to the application.properties file for testing (remember to create this file in src/test/resources/application.properties). This property indicates which version of MongoDB will be used for testing. In this case, I am using version 6.0.1.

``` properties
de.flapdoodle.mongodb.embedded.version=6.0.1
```
Now we can create our test. To do that, let's create the UserServiceTests class. The following code shows the declaration of this class:

```
@AutoConfigureDataMongo
@SpringBootTest
public class UserServiceTests {


    @Autowired
    private UserService userService;

    @Autowired
    private MongoClient mongoClient;

}
```

To run the tests, we need to add some data to the embedded database. We can use the @BeforeEach annotation with a method that will be executed before the tests. This method adds some users to MongoDB. Note that I am also clearing the user collection before inserting the data. This is optional and depends on what you want to achieve with your tests:

```java
@BeforeEach
void setup() {
    MongoDatabase database = mongoClient.getDatabase("test");
    MongoCollection<Document> collection = database.getCollection("user");

    collection.deleteMany(new Document());

    List<Document> documents = new ArrayList<>();
    for (int i = 0; i < 5; i++) {

        Document document = new Document();
        document.put("name", "Eduardo");
        document.put("city", "SP");
        document.put("age", 20);
        document.put("salary", 1000 * (i + 1));

        documents.add(document);
    }

    collection.insertMany(documents);

}
```

Finally, we can create and run the tests. This part is straightforward since we are not mocking any classes, and the data has already been inserted into the database in the previous method. So, basically, we just need to call the method we want to test and then verify if the aggregation data came as expected:

```java
@Test
void test_aggregateUsersSuccessfully() {
    List<CityAggregationDTO> page = userService
            .getAvg();

    Assertions.assertEquals("SP", page.get(0).getCity());
    Assertions.assertEquals(20, page.get(0).getAvgAge());
    Assertions.assertEquals(3000, page.get(0).getAvgSalary());
}
```