1

I was asked to create an integration test for a service within a very large SpringBoot project yielding dozens of implemented services. When the application is executed all of these services are deployed - I want to avoid deploying all services unrelated with the one for which I'm creating the test. Unfortunately, I do not (yet) have as much experience with spring boot tests as I would hope for hence I was wondering what is the best approach to address this.

I was thinking about annotating all unrelated services with @MockBean annotation and all related ones with @Autowire inside the test class, but I'm not sure if this is the proper way to go. Can anyone point me in the right direction?

Thank you.

João Matos
  • 6,102
  • 5
  • 41
  • 76
  • What should be the outcome for the integration tests? What part should be tested? If you use `@MockBean` for all `@Autowire` than you have heavy, slow UnitTests. In this case Mockito Unit Tests are better. – Matthias Feb 12 '20 at 16:29
  • We already have mockito unit tests. Now we want an integration test that i) deploys the target service and only that service; ii) performs a http request to it; iii) expects some result from it; – João Matos Feb 12 '20 at 16:36

3 Answers3

3

The answer largely depends on the scope of your integration test. I will try to cover two main ways and you can google your wait our for more examples and details. Spring Boot testing documentation is also your friend.

Slices

Spring Boot provides test utilities called slices. For example there's a slice for testing your controllers - @WebMvcTest - this test will load all configuration for calling your application from HTTP and your specified controller (@WebMvcTest(YourController.class)). After that you need to decide what to do with dependencies of that controller.

You can:

  • Mock them with @MockBean.
  • Provide real implementation (or additional configuration) with @Import (and then you have to again deal with dependencies of the newly imported dependency).
  • Load additional part of Spring Boot auto-configuration. This can be done using @AutoConfigureSomething annotations. - All slices are basically composites of autoconfigure annotations and you are free to add them to your tests. For example have a look at annotations on DataJpaTest for what it takes to add capability to setup Spring Boot Data JPA with test database.

You can have maximum one slice per your test but you can import any number of additional services, configurations, mocks, auto-configurations etc. The point is - you choose what is configured for your test; new unrelated services with new dependencies should not break existing tests.

SpringBootTest

Another approach is @SpringBootTest annotation - this goes in the opposite direction - by default it loads everything and you can exclude stuff you don't want with @MockBean, @EnableAutoConfiguration(exclude=SomeClass) etc.

There's of course a danger of breaking existing tests when adding new services. - This should not happen too often as everything is configured automatically but it's still a possibility especially in monolith with more configuration.

Josef Cech
  • 2,115
  • 16
  • 17
  • Thanks Josef, I already tried (successfully) option 2 but I was not happy with what you describe as 'danger'. I will try option 1 asap. – João Matos Feb 12 '20 at 17:43
  • @JoãoMatos Good luck. .) Just something to be careful about: Test slices will also load configuration from your main application class - the one with `@SpringBootApplication` - so if you have a ComponentScan there, explicit enabling of some auto-configuration etc then just move it to a separate `@Configuration` class. – Josef Cech Feb 12 '20 at 17:49
  • josef, WebMvcTest seems to be making my life a lot easier, nevertheless I noticed that there are still some unnecessary controllers being loaded. How do you propose to bypass them? Using @MockBean? Thanks! – João Matos Feb 13 '20 at 16:25
  • @JoãoMatos You can choose which controllers to load with this WeMvcTest annotation field: https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html#controllers-- – Josef Cech Feb 13 '20 at 18:14
  • I know, but still it is loading a lot more controllers that are unrelated to my test. I am struggling to find out why – João Matos Feb 13 '20 at 18:16
  • And then I find this: https://stackoverflow.com/a/43829617/4828060 which is disappointing - if that answer is correct then I don't see the advantage of using '@WebMvcTest' as opposed to the other approach mentioned – João Matos Feb 13 '20 at 18:22
  • @JoãoMatos This is hard to tell without seeing your configuration. From my experience - loading unwanted stuff was always a result of ComponentScan without correct excludeFilter or additional configuration on the main application class. For the answer you have linked - I don't think it contradicts this answer; can you eloborate what is the dealbreaker? – Josef Cech Feb 13 '20 at 18:31
  • That answer states that I have to mock all other controllers (the ones that I do not want to test) - what happens if someone later creates a new controller? Does it require to be added as a MockBean to previously existing tests? – João Matos Feb 13 '20 at 18:34
  • You don't need to mock controllers away. If the mention annotation field would be working for you then only that chosen controller would be loaded and adding new controller later would have no effect. Since it's not working then I would personally focus on fixing the root problem or there'll be other surprises in the future. .) – Josef Cech Feb 13 '20 at 18:40
  • Thank Josef! Just to clarify: I assign ‘@WebMvcTest’ with my target controller and when I run the test it crashes complaining about other controllers/services/repositories, thereby forcing me to use ‘@mockbean’ on them – João Matos Feb 13 '20 at 21:04
  • I created a new question for this issue: https://stackoverflow.com/q/60224814/4828060 – João Matos Feb 14 '20 at 10:51
  • I ended up figuring out my problem: https://stackoverflow.com/q/60260228/4828060 – João Matos Feb 17 '20 at 10:23
0

If you have to create spring integrations tests you have to : - invoke spring Context by using annotation on test class - for instance : @RunWith(SpringJUnit4ClassRunner.class) - use @MockBean or @SpyBean annotation on services that you are not going to test but they are part of testing methods/class - use @Autowired annotation on class that you are going to test. To verify results you can use Junit4 or Junit5 asserts and for verifying behavior you can use Mockito

Kamkle65
  • 29
  • 1
  • 3
  • Thank you for your answer. I'm a bit worried about the amount of services that will be annotated with '@MockBean' (several dozens) + what happens when a colleague decides to add a new service to the application, does he/she have to add it with '@MockBean' to every existing test in the project? – João Matos Feb 12 '20 at 16:45
  • Although I can live with the first part, I cannot live with the second. I need a solution for this. – João Matos Feb 12 '20 at 17:40
0

I had a similar issue of wanting to be able to isolate the integration testing of a particular component (or service) in Springboot; my constraint is that I have to use JUnit5 and Mockito.

I found these 2 posts from Baeldung which as usual only gives a specific answer to a specific scenario and not a general solution: https://www.baeldung.com/injecting-mocks-in-spring https://www.baeldung.com/junit-5-runwith

Long story short I found a solution as follows:

  1. Create a config class as follows for your component(s) in a test folder for your integration test (the example below is my wanting to integration test an Interceptor component; I have redacted names): enter image description here

  2. You can mock out beans, or you can instantiate beans that have no dependencies (leaf beans so to say)

  3. Then in your integration test you can use the latter config. in order to then get your component under test (cut) have its dependencies satisfied. My Interceptor had 3 levels of dependencies: enter image description here

  4. Finally, in your integration tests you can use the following to mock out method calls of the beans you mocked out: when(cut.getInitRequestRepo().initRequest(ArgumentMatchers.any(InParamInitRequest.class))).thenReturn(outParamInitRequest);

The POM dependencies I used:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
            <scope>test</scope>
        </dependency>
 <!-- Testing with Mocks -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>3.11.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>3.11.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.10.0</version>
            <scope>test</scope>
        </dependency>
Beezer
  • 1,084
  • 13
  • 18