Search code examples
springspring-bootjunitspring-data-mongodbspring-boot-test

Spring Boot test does not start context or load dependencies


A very beginner problem but one I can't get past. I have a basic Spring Boot app, and one Spring Data MongoDB repository that connects to the cloud atlas instance. The problem is that in my Spring Boot test, my repository is not autowired and the embedded MongoDB instance is not created. If I start the Spring Boot app and in the main class I autowire the repository, that works. Why does it not work in my test?

This is my test class:

@DataMongoTest
@ExtendWith(SpringExtension.class)
public class SampleServiceTest{


    @Autowired
    private SampleRepository sampleRepository;

    @Test
    public void shouldCreateSample(){
        sampleRepository.save(new Sample());
    }
}

This is my pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath></relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>



    <groupId>com.comand</groupId>
    <artifactId>business-owner-service</artifactId>
    <version>1.0-SNAPSHOT</version>
    <description>API Gateway</description>

    <properties>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>de.flapdoodle.embed</groupId>
            <artifactId>de.flapdoodle.embed.mongo</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-parent</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Solution

  • When you use the @Autowired annotation, basically you are mapping the variable with an object that exists in the application context. Remember the application context is created when you start the spring boot application. All classes that have the annotation @Service, @Repository, @Component are instantiated in the application context.

    I assume that SampleRepository has one of the following annotations :@Service, @Repository, @Component @Repository. And when you start the spring boot application, the application context is created and has instantiated the SampleRepository class.

    The @Autowire annotation will map the object that is created in the application context with the variable that has the annotation @Autowire.

    The reason why It doesn't work in your test is that the object of the SampleRepository class does not exist. And you cannot map it to the variable that you have annotated with @Autowire.

    You can fix this in two ways:

    1. First solution would be to create the application context when you run the test class. I will suggest not to load the whole application context with all the object instantiated. It will be better to load only the part of the application context that is need in your test class.
    @EnableConfigurationProperties(SampleRepository.class)
       public class TestConfiguration {
    }
    
    @ExtendWith(SpringExtension.class)
    @SpringBootTest(classes = { TestConfiguration.class })
    public class SampleServiceTest{
    }
    
    1. The second solution is to modify the annotation @DataMongoTest like below:
    @DataMongoTest(includeFilters = @Filter(Service.class))
    //or
    @DataMongoTest(includeFilters = @Filter(Component.class))
    

    Using @DataMongoTest annotation will disable full auto-configuration and instead apply only configuration relevant to MongoDB tests. So the classes annotated with @Services,Component are not instantiated. includeFilters are a set of filters that can be used to add filtered beans to the application context.

    I suspect that you have annotated the class SampleRepository with a @Service or @Component annotation and that's why it did not create the instance of SampleRepository class.

    I looked your code in git repo, and modified as below:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest
    public class BusinessOwnerServiceTest {
    
        @Autowired
        private BusinessOwnerService businessOwnerService;
    
        @Autowired
        private BusinessOwnerRepository businessOwnerRepository;
    
        @Test
        public void shouldCreateNewBusinessOwner(){
           businessOwnerService.findBusinessOwnerByEmail("EMAIL@gmail.com");    
        }
    
    }
    
    

    Below it is the result: enter image description here

    Below it is the second solution:

    @RunWith(SpringJUnit4ClassRunner.class)
    @DataMongoTest(includeFilters = @Filter(Service.class))
    public class BusinessOwnerServiceTest {
    
        @Autowired
        private BusinessOwnerService businessOwnerService;
    
        @Autowired
        private BusinessOwnerRepository businessOwnerRepository;
    
        @Test
        public void shouldCreateNewBusinessOwner(){
           businessOwnerService.findBusinessOwnerByEmail("EMAIL@gmail.com");    
        }
    
    }
    

    Below is the result from the second solution: enter image description here