13.2.3 Testing reactive MongoDB repositories
The key to writing tests for MongoDB repositories is to annotate the test class with @DataMongoTest. This annotation performs a function similar to the @DataR2dbcTest annotation that we used earlier in this chapter. It ensures that a Spring application context is created with the generated repositories available as beans to be injected into the test. From there, the test can use those injected repositories to set up test data and perform other operations against the database.
For example, consider IngredientRepositoryTest in the next listing, which tests IngredientRepository, asserting that Ingredient objects can be written to and read from the database.
Listing 13.15 Testing a reactive Mongo repository
package tacos.data;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import tacos.Ingredient;
import tacos.Ingredient.Type;
@DataMongoTest
public class IngredientRepositoryTest {
@Autowired
IngredientRepository ingredientRepo;
@BeforeEach
public void setup() {
Flux<Ingredient> deleteAndInsert = ingredientRepo.deleteAll()
.thenMany(ingredientRepo.saveAll(
Flux.just(
new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
new Ingredient("CHED", "Cheddar Cheese", Type.CHEESE)
)));
StepVerifier.create(deleteAndInsert)
.expectNextCount(3)
.verifyComplete();
}
@Test
public void shouldSaveAndFetchIngredients() {
StepVerifier.create(ingredientRepo.findAll())
.recordWith(ArrayList::new)
.thenConsumeWhile(x -> true)
.consumeRecordedWith(ingredients -> {
assertThat(ingredients).hasSize(3);
assertThat(ingredients).contains(
new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
assertThat(ingredients).contains(
new Ingredient("GRBF", "Ground Beef", Type.PROTEIN));
assertThat(ingredients).contains(
new Ingredient("CHED", "Cheddar Cheese", Type.CHEESE));
})
.verifyComplete();
StepVerifier.create(ingredientRepo.findById("FLTO"))
.assertNext(ingredient -> {
ingredient.equals(new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
});
}
}This test is similar to, but still slightly different from, the R2DBC-based repository test we wrote earlier in this chapter. It starts by writing three Ingredient objects to the database. Then, it employs two StepVerifier instances to verify that Ingredient objects can be read through the repository, first as a collection of all Ingredient objects and then fetching a single Ingredient by its ID.
Also, just as with the R2DBC-based test from earlier, the @DataMongoTest annotation will seek out a @SpringBootConfiguration-annotated class for creating the application context. A test just like the one created earlier will work here, too.
What’s unique here is that the first StepVerifier collects all of the Ingredient objects into an ArrayList and then asserts that the ArrayList contains each Ingredient. The findAll() method doesn’t guarantee a consistent ordering of the resulting documents, which makes the use of assertNext() or expectNext() prone to fail. By collecting all resulting Ingredient objects into a list, we can assert that the list has all three objects, regardless of their order.
A test for OrderRepository looks quite similar, as shown here.
Listing 13.16 Testing the Mongo OrderRepository
package tacos.data;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import reactor.test.StepVerifier;
import tacos.Ingredient;
import tacos.Taco;
import tacos.TacoOrder;
import tacos.Ingredient.Type;
@DataMongoTest
public class OrderRepositoryTest {
@Autowired
OrderRepository orderRepo;
@BeforeEach
public void setup() {
orderRepo.deleteAll().subscribe();
}
@Test
public void shouldSaveAndFetchOrders() {
TacoOrder order = createOrder();
StepVerifier
.create(orderRepo.save(order))
.expectNext(order)
.verifyComplete();
StepVerifier
.create(orderRepo.findById(order.getId()))
.expectNext(order)
.verifyComplete();
StepVerifier
.create(orderRepo.findAll())
.expectNext(order)
.verifyComplete();
}
private TacoOrder createOrder() {
TacoOrder order = new TacoOrder();
...
return order;
}
}The first thing that the shouldSaveAndFetchOrders() method does is construct an order, complete with customer and payment information and a couple of tacos. (For brevity’s sake, I’ve left out the details of the createOrder() method.) It then uses a StepVerifier to save the TacoOrder object and assert that the save() method returns the saved TacoOrder. It then attempts to fetch the order by its ID and asserts that it receives the full TacoOrder. Finally, it fetches all TacoOrder objects—there should be only one—and asserts it is the expected TacoOrder.
As mentioned earlier, you’ll need a MongoDB server available and listening on port 27017 to run this test. The Flapdoodle embedded MongoDB doesn’t work well with reactive repositories. If you have Docker installed on your machine, you can easily start a MongoDB server exposed on port 27017 like this:
docker run -p27017:27017 mongoOther ways to get a MongoDB setup are possible. Consult the documentation at https://www.mongodb.com/ for more details.
Now that we’ve seen how to create reactive repositories for R2BDC and MongoDB, let’s have a look at one more Spring Data option for reactive persistence: Cassandra.
