How Do I Manually Wire A Spring Boot Integration Test? - spring-boot

Normally, I would test the web layer in a Spring project like this:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SpringBootDemoApplicationTests extends AbstractTestNGSpringContextTests {
#LocalServerPort
int randomServerPort;
#Autowired
private TestRestTemplate restTemplate;
However, I currently have a difficult back end that requires a specific #TestConfiguration class to manually instantiate the test dependencies using beans.
This ultimately means that I can't use the #SpringBootTest annotation as it will try to create conflicting beans and fail to instantiate others.
If I am not using the #SpringBootTest annotation, I can manually create the TestRestTemplate instead of autowiring it, but what do I need to do to start the embedded local server on a random port?

I would still continue using #SpringBootTest, and combine that with using #Profile on your configuration classes.
That way you could have a configuration which is only used during tests, by using #ActiveProfiles on your #SpringBootTest classes. In the same way you can turn other config classes on or off depending on whether you want them to load or not.
For example on your test would have the following
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#ActiveProfiles("unittest")
public class SpringBootDemoApplicationTests extends AbstractTestNGSpringContextTests {
...
}
Then create a configuration class which will instantiate your components the way you want them in test
#Profile("unittest")
#Configuration
public void TestConfiguration {
...
}
And you can use profiles to stop your other configuration class from loading during tests.
#Profile("!unittest")
#Configuration
public void ProdConfiguration {
...
}

Related

PostConstruct and test

I use spring boot 3
Main spring boot class
#EnableTransactionManagement
#SpringBootApplication
#Slf4j
public class FlexApplication{
private final ApplicationParameterManager appParamManager;
public FlexApplication(ApplicationParameterManager appParamManager) {
this.appParamManager = appParamManager;
}
#PostConstruct
public void init(){
}
....
}
#Service
#Slf4j
public class ApplicationParameterManager{
....
}
Basic test
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
Otherwise this test, application run well
I get this error
Parameter 0 of constructor in com.acme.FlexApplication required a bean
of type 'com.acme.parameter.ApplicationParameterManager' that could
not be found.
I think it is not related to version of Spring Boot.
As you're using #DataJpaTest , your bean is not created
Spring Docs:
#DataJpaTest can be used if you want to test JPA applications. By
default it will configure an in-memory embedded database, scan for
#Entity classes and configure Spring Data JPA repositories. Regular
#Component beans will not be loaded into the ApplicationContext.
Solution would be to use #SpringBootTest instead of #DataJpaTest if your test is not really a JPA test.
Also, still using #DataJpaTest you could add #Import(ApplicationParameterManager.class) to your test class
When using #DataJpaTest, you are not creating the whole spring context as when you run the application normally but you only create the beans responsible for data access layer.
In order to run your tests properly, you need to provide a mocked bean of type ApplicationParameterManager.
The easiest way to do it is by utilizing #MockBean annotation.
So, to make your tests work, edit the test in the following way.
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#MockBean
private ApplicationParameterManager applicationParameterManager;
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
That way, the spring context will include a mocked bean of your required dependency.
Take a look at #MockBean java doc for more information. https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
If you prefer to run the whole spring context in order to perform full integration tests, take a look at #SpringBootTest annotation.
#DataJpaTest should be used when you want to test data access layer in isolation.

How to disable #Configuration initialization in WebFluxTest?

I would like to write tests for reactive controller using #WebFluxTest annotation, mocking all dependencies.
#WebFluxTest(controllers = MyController.class)
public class MyControllerTest {
#MockBean
SomeService service;
#Autowired
WebTestClient webClient;
//some tests
}
From what I understand, the WebFluxTest annotation shall apply only configuration relevant to WebFlux tests (i.e. #Controller, #ControllerAdvice, etc.), but not another beans.
My spring boot app contains a number of #Configuration classes that configure a number of beans (annotated as #Bean). Some of those configurations have also dependencies (autowired by constructor).
#Configuration
#RequiredArgsConstructor
public class MyConfig {
private final AnotherConfig anotherConfig;
#Bean
//...
}
When I run my web flux tests, I can see the context initialization contains an attempt to initialize the MyConfig (and it fails because of the missing dependency which comes from 3rd party auto-configured lib). How can I configure the test to skip initialization of all of these?
I am able to exclude the problematic configuration class only by excluding auto configuration of the whole app.
#WebFluxTest(controllers = MyController.class, excludeAutoConfiguration = {MyApplication.class})
public class MyControllerTest { ... }
where MyApplication is the spring boot app autoscanning those configuration classes.
But how can I achieve to skip initialization of MyConfig only? Or even better, how can I achieve to only include a list of configurations to be initialized?
Add
#ActiveProfiles("YOUR_ENV_OTHER_THAN_TEST")
below or above #Configuration
For multiple environments..
#ActiveProfiles(profiles ={env1, env2,env3})

Write Unit test in SpringBoot Without start application

Am developing MicroServices in springBoot. Am writing unit test for Service and DAO layer. When I use #SpringBootTest it starting application on build. But It should not start application
when I run unit test. I used #RunWith(SpringRunner.class), But am unable to #Autowired class instance in junit class. How can I configure junit test class that should not start application and how to #Autowired class instance in junit class.
Use MockitoJUnitRunner for JUnit5 testing if you don't want to start complete application.
Any Service, Repository and Interface can be mocked by #Mock annotation.
#InjectMocks is used over the object of Class that needs to be tested.
Here's an example to this.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
public class AServiceTest {
#InjectMocks
AService aService;
#Mock
ARepository aRepository;
#Mock
UserService userService;
#Before
public void setUp() {
// MockitoAnnotations.initMocks(this);
// anything needs to be done before each test.
}
#Test
public void loginTest() {
Mockito.when(aRepository.findByUsername(ArgumentMatchers.anyString())).thenReturn(Optional.empty());
String result = aService.login("test");
assertEquals("false", result);
}
With Spring Boot you can start a sliced version of your application for your tests. This will create a Spring Context that only contains a subset of your beans that are relevant e.g. only for your web layer (controllers, filters, converters, etc.): #WebMvcTest.
There is a similar annotation that can help you test your DAOs as it only populates JPA and database relevant beans (e.g. EntitiyManager, Datasource, etc.): #DataJpaTest.
If you want to autowire a bean that is not part of the Spring Test Context that gets created by the annotatiosn above, you can use a #TestConfiguration to manually add any beans you like to the test context
#WebMvcTest(PublicController.class)
class PublicControllerTest {
#Autowired
private MockMvc mockMvc;
#TestConfiguration
static class TestConfig {
#Bean
public EntityManager entityManager() {
return mock(EntityManager.class);
}
#Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
}
}
Depending your test setup, if you don't want to autowire a mock but the "real thing", You could simply annotate your test class to include exactly the classes you need (plus their transitive dependencies if necessary)
For example :
#SpringJUnitConfig({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#Import({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#ContextConfiguration(classes = { SimpleMeterRegistry.class })
See working JUnit5 based samples in here Spring Boot Web Data JDBC allin .

Does #WebMvcTest require #SpringBootApplication annotation?

My goal is to migrate a Spring Boot application previously developed with Spring Boot 1.3 to the newest Spring Boot version 1.4. The application consists of several maven modules and only one of them contains class annotated with #SpringBootApplication.
One part of migration is to use #WebMvcTest annotation to efficiently test controllers, and here I get an issue.
Consider an example application from Spring Boot github page. #WebMvcTest annotation works perfectly, because, as far as I understand (after I did several tests), there is a class in the main package annotated with #SpringBootApplication. Note that I follow the same concept as shown in the example above for my own #WebMvcTest tests.
The only difference I see that in my application, controller classes are located in a separate maven module (without #SpringBootApplication annotated class), but with #Configuration and SpringBootConfiguration configurations. If I do not annotate any class with #SpringBootApplication I always get an assertion while testing controller. My assertion is the same as when SampleTestApplication class in the example above modified to have only #EnableAutoConfiguration and #SpringBootConfiguration annotations (#SpringBootApplication is not present):
getVehicleWhenRequestingTextShouldReturnMakeAndModel(sample.test.web.UserVehicleControllerTests) Time elapsed: 0.013 sec <<< FAILURE!
java.lang.AssertionError: Status expected:<200> but was:<404>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
at sample.test.web.UserVehicleControllerTests.getVehicleWhenRequestingTextShouldReturnMakeAndModel(UserVehicleControllerTests.java:68)
How should I deal with that? Should I always have class annotated with #SpringBootApplication in order to run #WebMvcTest tests?
EDIT 1: I did a small maven project with 2 modules and a minimal configuration. It is here. Now, I get NoSuchBeanDefinitionException exception for repository defined in another module. If I configure "full" #SpringBootApplication - everything is fine.
EDIT 2: I modified small test project from EDIT 1 to give an original issue. I was playing with different annotations and added #ComponentScan on configuration class, because I suspected that beans are not registered properly. However, I expect that only #Controller bean (defined in #WebMvcTest(...class)) shall be registered based on magic behind #WebMvcTest behaviour.
EDIT 3: Spring Boot project issue.
Short answer: I believe so.
Long answer:
I believe #WebMvcTest needs to find the SpringBootApplication configuration since WebMvcTest's sole purpose is to help simplify tests (SpringBootApplication would rather try to load the whole world).
In your specific case, since you don't have any in your non-test packages, I believe it also finds SampleTestConfiguration which is annotated with #ScanPackages and somehow loads every beans.
Add the following in src/main/java/sample/test
#SpringBootApplication
public class SampleTestConfiguration {
}
And change your test to this:
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private MyService ms;
#Autowired
private ApplicationContext context;
#Test
public void getDataAndExpectOkStatus() throws Exception {
given(ms.execute("1")).willReturn(false);
mvc.perform(get("/1/data").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk()).andExpect(content().string("false"));
}
#Test
public void testMyControllerInAppCtx() {
assertThat(context.getBean(MyController.class), is(not(nullValue())));
}
#Test
public void testNoMyAnotherControllerInAppCtx() {
try {
context.getBean(MyAnotherController.class);
fail("Bean exists");
} catch (BeansException e) {
// ok
}
}
}
#WebMvcTest finds the SpringBootApplication, then load only a limited number of beans (see documentation):
#WebMvcTest will auto-configure the Spring MVC infrastructure and
limit scanned beans to #Controller, #ControllerAdvice, #JsonComponent,
Filter, WebMvcConfigurer and HandlerMethodArgumentResolver. Regular
#Component beans will not be scanned when using this annotation.
WebMvcTest requires SpringBootApplication: WebMvcTest inherits many AutoConfiguration, so it needs SpringBoot to load them. Then it disables many other AutoConfiguration and your Controllers become easily testable.
The whole point of using WebMvcTest is when you have a SpringBootApplication and you wish to make it simpler to test by disabling all beans except Controllers. If you don't have SpringBootApplication, then why use WebMvcTest at all?
It's an old topic, but there is a solution which wasn't mentioned here.
You can create a class annotated with SpringBootApplication just in your test sources. Then, you still have a nice, multi-module structure of your project, with just one "real" SpringBootApplication.
Yes,according to the spring boot docs
The search algorithm works up from the package that contains the test until it finds a #SpringBootApplication or #SpringBootConfiguration annotated class. As long as you’ve structure your code in a sensible way your main configuration is usually found.
But after I started using #WebMvcTest,spring boot still try to load other beans, finally TypeExcludeFilter did the trick.
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = {JzYsController.class} )
public class JzYsControllerTest {
private static final String REST_V4_JZYS = "/rest/v4/JzYs";
#Autowired
private MockMvc mockMvc;
#MockBean
private JzYsService service;
#Test
public void deleteYsByMlbh() throws Exception {
Mockito.when(service.deleteYsByMlbh(Mockito.anyString())).thenReturn(Optional.of(1));
mockMvc.perform(delete(REST_V4_JZYS + "?mbbh=861FA4B0E40F5C7FECAF09C150BF3B01"))
.andExpect(status().isNoContent());
}
#SpringBootConfiguration
#ComponentScan(excludeFilters = #Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public static class config{
}
}
There is one more solution. You can not use #WebMvcTest, but configure MockMvc yourself through the builder
class TestControllerTest {
private MockMvc mvc;
#BeforeEach
public void setup() {
mvc = MockMvcBuilders.standaloneSetup(new TestController())
.build();
}
#Test
void test() throws Exception {
// When
var res = mvc.perform(MockMvcRequestBuilders.get("/test/test"));
// Then
res.andExpect(status().isOk());
}
}
But this solution may entail a number of other problems, such as problems with configurations, environment property injections, etc.

Reuse spring application context across junit test classes

We've a bunch of JUnit test cases (Integration tests) and they are logically grouped into different test classes.
We are able to load Spring application context once per test class and re-use it for all test cases in a JUnit test class as mentioned in http://static.springsource.org/spring/docs/current/spring-framework-reference/html/testing.html
However, we were just wondering if there is a way to load Spring application context only once for a bunch of JUnit test classes.
FWIW, we use Spring 3.0.5, JUnit 4.5 and use Maven to build the project.
Yes, this is perfectly possible. All you have to do is to use the same locations attribute in your test classes:
#ContextConfiguration(locations = "classpath:test-context.xml")
Spring caches application contexts by locations attribute so if the same locations appears for the second time, Spring uses the same context rather than creating a new one.
I wrote an article about this feature: Speeding up Spring integration tests. Also it is described in details in Spring documentation: 9.3.2.1 Context management and caching.
This has an interesting implication. Because Spring does not know when JUnit is done, it caches all context forever and closes them using JVM shutdown hook. This behavior (especially when you have a lot of test classes with different locations) might lead to excessive memory usage, memory leaks, etc. Another advantage of caching context.
To add to Tomasz Nurkiewicz's answer, as of Spring 3.2.2 #ContextHierarchy annotation can be used to have separate, associated multiple context structure. This is helpful when multiple test classes want to share (for example) in-memory database setups (datasource, EntityManagerFactory, tx manager etc).
For example:
#ContextHierarchy({
#ContextConfiguration("/test-db-setup-context.xml"),
#ContextConfiguration("FirstTest-context.xml")
})
#RunWith(SpringJUnit4ClassRunner.class)
public class FirstTest {
...
}
#ContextHierarchy({
#ContextConfiguration("/test-db-setup-context.xml"),
#ContextConfiguration("SecondTest-context.xml")
})
#RunWith(SpringJUnit4ClassRunner.class)
public class SecondTest {
...
}
By having this setup the context that uses "test-db-setup-context.xml" will only be created once, but beans inside it can be injected to individual unit test's context
More on the manual: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#testcontext-ctx-management (search for "context hierarchy")
One remarkable point is that if we use #SpringBootTests but again use #MockBean in different test classes, Spring has no way to reuse its application context for all tests.
Solution is to move all #MockBean into an common abstract class and that fix the issue.
#SpringBootTests(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
public abstract class AbstractIT {
#MockBean
private ProductService productService;
#MockBean
private InvoiceService invoiceService;
}
Then the test classes can be seen as below
public class ProductControllerIT extends AbstractIT {
// please don't use #MockBean here
#Test
public void searchProduct_ShouldSuccess() {
}
}
public class InvoiceControllerIT extends AbstractIT {
// please don't use #MockBean here
#Test
public void searchInvoice_ShouldSuccess() {
}
}
Basically spring is smart enough to configure this for you if you have the same application context configuration across the different test classes. For instance let's say you have two classes A and B as follows:
#ActiveProfiles("h2")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {
#MockBean
private C c;
//Autowired fields, test cases etc...
}
#ActiveProfiles("h2")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {
#MockBean
private D d;
//Autowired fields, test cases etc...
}
In this example class A mocks bean C, whereas class B mocks bean D. So, spring considers these as two different configurations and thus would load the application context once for class A and once for class B.
If instead, we'd want to have spring share the application context between these two classes, they would have to look something as follows:
#ActiveProfiles("h2")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {
#MockBean
private C c;
#MockBean
private D d;
//Autowired fields, test cases etc...
}
#ActiveProfiles("h2")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {
#MockBean
private C c;
#MockBean
private D d;
//Autowired fields, test cases etc...
}
If you wire up your classes like this, spring would load the application context only once either for class A or B depending on which class among the two is ran first in the test suite. This could be replicated across multiple test classes, only criteria is that you should not customize the test classes differently. Any customization that results in the test class to be different from the other(in the eyes of spring) would end up creating another application context by spring.
create your configuaration class like below
#ActiveProfiles("local")
#RunWith(SpringJUnit4ClassRunner.class )
#SpringBootTest(classes ={add your spring beans configuration classess})
#TestPropertySource(properties = {"spring.config.location=classpath:application"})
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
public class RunConfigration {
private ClassLoader classloader = Thread.currentThread().getContextClassLoader();
private static final Logger LOG = LoggerFactory.getLogger(S2BXISINServiceTest.class);
//auto wire all the beans you wanted to use in your test classes
#Autowired
public XYZ xyz;
#Autowired
public ABC abc;
}
Create your test suite like below
#RunWith(Suite.class)
#Suite.SuiteClasses({Test1.class,test2.class})
public class TestSuite extends RunConfigration {
private ClassLoader classloader = Thread.currentThread().getContextClassLoader();
private static final Logger LOG = LoggerFactory.getLogger(TestSuite.class);
}
Create your test classes like below
public class Test1 extends RunConfigration {
#Test
public void test1()
{
you can use autowired beans of RunConfigration classes here
}
}
public class Test2a extends RunConfigration {
#Test
public void test2()
{
you can use autowired beans of RunConfigration classes here
}
}

Resources