Testing with embedded MongoDB

The first annotation listed above for slice testing is @DataMongoTest. In this section, we want to write some test methods that involve our MongoDB-specific code.

When it comes to testing MongoDB code, we have the following two options provided by Spring Boot:

  • Testing against an embedded MongoDB instance
  • Testing against a live MongoDB instance

Spring Boot, by default, will check if Flapdoodle, the embedded MongoDB database, is on the classpath. If so, it will attempt to run the test using it. If Flapdoodle is NOT on our classpath, it will attempt to connect to a real MongoDB instance.

So let's get started by adding flapdoodle to our project's list of dependencies as follows:

    testCompile("de.flapdoodle.embed:de.flapdoodle.embed.mongo") 

Since we are going to test our Reactor-based APIs, we also want to leverage Reactor Test, a library of utilities provided by Project Reactor. Let's add the following test dependency:

    testCompile("io.projectreactor:reactor-test") 

With this last dependency added to our project, we can now start writing EmbeddedImageRepositoryTests.java inside src/test/java, in the com.greglturnquist.learningspringboot package, like this:

    @RunWith(SpringRunner.class) 
    @DataMongoTest 
    public class EmbeddedImageRepositoryTests { 
 
      @Autowired 
      ImageRepository repository; 
 
      @Autowired 
      MongoOperations operations; 

The preceding code for the first part of this test class can be described as follows:

  • @RunWith(SpringRunner.java) is needed to ensure that Spring Boot test annotations run properly within JUnit
  • @DataMongoTest will disable the general Spring Boot autoconfiguration, and instead, use Spring Boot's test-based autoconfigurations to create a MongoTemplate, a MongoDB connection, MongoDB property settings, a ReactiveMongoTemplate and an embedded MongoDB instance; it will also enable the MongoDB repositories
  • With the Spring Data MongoDB repositories enabled, Spring Boot will automatically instantiate an ImageRepository, and inject it into our autowired repository field
In general, it's recommended to use constructor injection for production code. But for test code where constructors are limited due to JUnit, autowiring as we've just done is fine.

With access to a clean MongoDB instance (embedded), we can now perform a little setup work as follows:

    /** 
    * To avoid {@code block()} calls, use blocking 
    * {@link MongoOperations} during setup. 
    */ 
    @Before 
    public void setUp() { 
      operations.dropCollection(Image.class); 
      operations.insert(new Image("1", 
        "learning-spring-boot-cover.jpg")); 
      operations.insert(new Image("2", 
        "learning-spring-boot-2nd-edition-cover.jpg")); 
      operations.insert(new Image("3", 
        "bazinga.png")); 
      operations.findAll(Image.class).forEach(image -> { 
        System.out.println(image.toString()); 
      }); 
    } 

This preceding setup method can be described as follows:

  • The @Before flags this method to be run before every single @Test method in this class
  • The operations is used to dropCollection and then insert three new entries in the database, turn around and fetch them all, and print them to the console

With things preloaded properly, we can start writing our first test case, as shown next:

    @Test 
    public void findAllShouldWork() { 
      Flux<Image> images = repository.findAll(); 
      StepVerifier.create(images) 
       .recordWith(ArrayList::new) 
       .expectNextCount(3) 
       .consumeRecordedWith(results -> { 
         assertThat(results).hasSize(3); 
         assertThat(results) 
         .extracting(Image::getName) 
         .contains( 
           "learning-spring-boot-cover.jpg", 
           "learning-spring-boot-2nd-edition-cover.jpg", 
           "bazinga.png"); 
         }) 
       .expectComplete() 
       .verify(); 
    } 

This preceding test case can be described as follows:

  • @Test indicates this is a test method and the method name describes our overall goal.
  • We use Reactor Test's StepVerifier to subscribe to the Flux from the repository and then assert against it.
  • Because we want to assert against the whole collection, we need to pipe it through Reactor Test's recordWith method, which fetches the entire Flux and converts it into an ArrayList via a method handle.
  • We verify that there were indeed three entries.
  • We write a lambda to peek inside the recorded ArrayList. In it, we can use AssertJ to verify the size of ArrayList as well as extract each image's name with Image::getName and verify them.
  • Finally, we can verify that Flux emitted a Reactive Streams complete signal, meaning that it finished correctly.

StepVerifier speaks Reactive Streams and will execute all the various signals to talk to the enclosed Publisher. In this case, we interrogated a Flux but this can also be used on a Mono.

To wrap things up, we are going to test our custom finder, findByName, as shown here:

    @Test 
    public void findByNameShouldWork() { 
      Mono<Image> image = repository.findByName("bazinga.png"); 
      StepVerifier.create(image) 
       .expectNextMatches(results -> { 
         assertThat(results.getName()).isEqualTo("bazinga.png"); 
         assertThat(results.getId()).isEqualTo("3"); 
         return true; 
      }); 
    } 

This last test case can be described as follows:

  • repository.findByName() is used to fetch one record
  • We again use StepVerifier to create a subscriber for our Mono and then expect the next signal to come through, indicating that it was fetched
  • Inside the lambda, we perform a couple of AssertJ assertions to verify the state of this Image
Due to the functional nature of StepVerifier, we need to return a Boolean representing pass/fail.

By the way, exactly how many CRUD methods do we need to test? We covered findAll and findByName. In principle, we could sidestep findAll since that can be considered a part of Spring Data MongoDB. But it makes a good example in this book for testing a Reactor Flux result.

In general, we shouldn't bite off testing framework code. But verifying our custom finder makes perfect sense. And there's always room for end-to-end testing, which we'll explore further in this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset