I am testing a Spring Boot application. I have several test classes, each of which needs a different set of mocked or otherwise customized beans.
Here is a sketch of the setup:
src/main/java:
package com.example.myapp; @SpringBootApplication @ComponentScan( basePackageClasses = { MyApplication.class, ImportantConfigurationFromSomeLibrary.class, ImportantConfigurationFromAnotherLibrary.class}) @EnableFeignClients @EnableHystrix public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } package com.example.myapp.feature1; @Component public class Component1 { @Autowired ServiceClient serviceClient; @Autowired SpringDataJpaRepository dbRepository; @Autowired ThingFromSomeLibrary importantThingIDontWantToExplicitlyConstructInTests; // methods I want to test... }
src/test/java:
package com.example.myapp; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MyApplication.class) @WebAppConfiguration @ActiveProfiles("test") public class Component1TestWithFakeCommunication { @Autowired Component1 component1; // <-- the thing we're testing. wants the above mock implementations of beans wired into it. @Autowired ServiceClient mockedServiceClient; @Configuration static class ContextConfiguration { @Bean @Primary public ServiceClient mockedServiceClient() { return mock(ServiceClient.class); } } @Before public void setup() { reset(mockedServiceClient); } @Test public void shouldBehaveACertainWay() { // customize mock, call component methods, assert results... } } package com.example.myapp; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MyApplication.class) @WebAppConfiguration @ActiveProfiles("test") public class Component1TestWithRealCommunication { @Autowired Component1 component1; // <-- the thing we're testing. wants the real implementations in this test. @Autowired ServiceClient mockedServiceClient; @Before public void setup() { reset(mockedServiceClient); } @Test public void shouldBehaveACertainWay() { // call component methods, assert results... } }
The problem with the above setup is that the component scan configured in MyApplication picks up Component1TestWithFakeCommunication.ContextConfiguration, so I get a mock ServiceClient even in Component1TestWithRealCommunication where I want the real ServiceClient implementation.
Although I could use @Autowired constructors and build up the components myself in both tests, there is a sufficient amount of stuff with complicated setup that I would rather have Spring TestContext set up for me (for example, Spring Data JPA repositories, components from libraries outside the app that pull beans from the Spring context, etc.). Nesting a Spring configuration inside the test that can locally override certain bean definitions within the Spring context feels like it should be a clean way to do this; the only downfall is that these nested configurations end up affecting all Spring TestContext tests that base their configuration on MyApplication (which component scans the app package).
How do I modify my setup so I still get a "mostly real" Spring context for my tests with just a few locally overridden beans in each test class?
The @ComponentScan annotation is used with the @Configuration annotation to tell Spring the packages to scan for annotated components. @ComponentScan also used to specify base packages and base package classes using thebasePackageClasses or basePackages attributes of @ComponentScan.
@Component and @ComponentScan are for different purposes. @Component indicates that a class might be a candidate for creating a bean. It's like putting a hand up. @ComponentScan is searching packages for Components.
One of the most important annotations in spring is @ComponentScan which is used along with the @Configuration annotation to specify the packages that we want to be scanned. @ComponentScan without arguments tells Spring to scan the current package and all of its sub-packages.
@ComponentScan tells Spring in which packages you have annotated classes which should be managed by Spring. Spring needs to know which packages contain spring beans, otherwise you would have to register each bean individually in(xml file). This is the use of @ComponentScan.
The following should help you to achieve your goal by introducing a new fake-communication
profile that is applicable only to the current test class.
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MyApplication.class) @WebAppConfiguration @ActiveProfiles({"test", "fake-communication"}) public class Component1TestWithFakeCommunication { // @Autowired ... @Profile("fake-communication") @Configuration static class ContextConfiguration { @Bean @Primary public ServiceClient mockedServiceClient() { return mock(ServiceClient.class); } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With