I have to implement some test for some spring application. I am using #SpringBootTest annotation in my test:
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest(){
//some tests...
}
It works fine, but i do not want to load all application context and and limit it by adding one or more nessecary configuration class. I done it with #ContextHierarchy:
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ContextHierarchy(ContextConfiguration(classes = [SomeCofigClass1::class, SomeConfigClass2::class]))
class MyTest(){
//some tests...
}
//for example
class SomeCofigClass1(){
#Bean
fun(someMongoRepository: SomeMongoRepository){ \\<-- Problem is here
return SomeService(someMongoRepository)
}
}
/**
* My repository.
*/
interface SomeMongoRepository : MongoRepository<Job, String> {}
Because of context is partially loaded i got a error:
No qualifying bean of type 'SomeMongoRepository' available:....
How can i load repository in test application context?
I already tried:
1) Added #AutoConfigureDataMongo. I got error java.lang.IllegalStateException: Unable to retrieve #EnableAutoConfiguration base packages
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ContextHierarchy(ContextConfiguration(classes = [SomeCofigClass1::class, SomeConfigClass2::class]))
#AutoConfigureDataMongo
2) Replaced #SpringBootTest by #DataMongoTest. I got error Unable to retrieve #EnableAutoConfiguration base packages
#DataMongoTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ContextHierarchy(ContextConfiguration(classes = [SomeCofigClass1::class, SomeConfigClass2::class]))
3) With #DataMongoTest replaced #ContextHierarchy by #Import. With #Import annotation it loads all application context. This is not suit for me.
#DataMongoTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#Import(SomeCofigClass1::class, SomeConfigClass2::class)
Add #RunWith(SpringRunner.class) to load Spring's ApplicationContext during the test.
Furthermore, classes can be selected by using the #SpringBootTest#classes attribute (or #ContextConfiguration):
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {SomeCofigClass1.class, SomeConfigClass2.class})
If your test makes use of inner Configuration classes, be sure to make them static (and add #Configuration). From the documentation:
If you omit the classes attribute from the #ContextConfiguration annotation, the TestContext framework tries to detect the presence of
default configuration classes. Specifically,
AnnotationConfigContextLoader and AnnotationConfigWebContextLoader
detect all static nested classes of the test class that meet the
requirements for configuration class implementations, as specified in
the #Configuration javadoc.
Related
My tests of a Spring application are written using JUnit 4. To ease writing of JUnit tests and reduce repetitive code, I use a base class (AbstractDaoTest) that defines a basic Spring application context using annotation #ContextConfiguration(classes = …):
#ContextConfiguration(classes = TestDbConf.class)
public abstract class AbstractDaoTest {
...
}
Subclasses extend the application context by defining extra annotation ContextConfiguration(…) with additional classes:
#ContextConfiguration(classes = SomeExtraBean.class)
public class SomeTestClass extends AbstractDaoTest {
...
}
JUnit 5 came with the modern approach "composition is better than inheritance".
I like the idea so I tried to avoid inheritance by defining a composed annotation:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestDbConf.class)
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
public #interface DaoTest { }
… and using the annotation on a test class:
#DaoTest
#ContextConfiguration(classes = ExtraBeanClass.class)
public class SomeTest {
...
}
But when I execute it, it seems that the #ContextConfiguration annotation causes the application context to be shadowed/overwritten, i.e. the context defined by annotation #DaoTest is not used.
What is the correct approach? Is it possible to achieve the goal using JUnit 5 extension?
I searched the internet and read several articles about JUnit 5 extensions and JUnit 5 in general but I couldn't find the answer in any of them.
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 {
...
}
I've a #RestController which has only one dependency in field #Autowire
that dependency is #component, that component Class definition has some autowired fields which are #service and those services have some #repositories.
In this whole flow I've kafka, Quartz, Cassandra and DB2
So when I was creating a Unit test case for my controller, I dont want to setup whole application. so I decided to use #webMvcTest and used #MockBean on my only one dependency of controller class.
But my Test is throwing and exception because its trying to create a Dao bean, which is marked as #repository.
#ActiveProfiles("test")
#WebMvcTest(controllers = MyControllerTest .class)
class MyControllerTest {
#MockBean
MyControllerDependency dependency;
#Autowired
MockMvc mockMvc;
#Test
void test_something() throws Exception {
assert(true);
}
}
Here is oversimplified version of code
#Component
class MyControllerDependency {
#AutoiWired
MyCustomService service;
}
#Service
class MyCustomService{
#Autowired
MyCustomDao dao;
}
#Repository
class MyCustomDao{
#Autowired
private JdbcTemplate template;
}
I'm getting following exception in test.
Exception
***************************
APPLICATION FAILED TO START
***************************
Description:
Field template in com.....MyCustomDao` required a bean of type 'org.springframework.jdbc.core.JdbcTemplate' that could not be found.
Question is, When I'm using #WebMvcTest slice and already mocking the only required dependency MyControllerDependency then why spring test context is trying to load MyCustomDao which is annotated as #Repository.
I can do integration testing with SpringbootTest & AutoconfigureMockMVC, but for writing Junit test just for controller, I need to use WebMvcTest slice. which is creating a problem.
I ran into a similar problem where I want to test only my controller using #WebMvcTest, but the spring context was trying to create nondependent spring beans and was failing as below.
Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'TestController' defined in file ...
Solution: load only the controller your testing for an example like #ContextConfiguration(classes = DemoController.class).
Also, find below a complete sample
#WebMvcTest
#ContextConfiguration(classes = DemoController.class)
public class DemoControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
DemoService demoService;
#Test
public void testGetAllProductCodes_withOutData() throws Exception {
when(productCodeService.getAllProductCodes()).thenReturn(new ArrayList<ProductCodes>());
mockMvc.perform(MockMvcRequestBuilders.get("/services/productCodes")).andExpect(MockMvcResultMatchers.status().isNoContent());
}
}
}
Do you have any #ComponentScan("...") annotation active on your #SpringBootApplication?
As described in Spring Boot Reference Documentation:
Another source of confusion is classpath scanning. Assume that, while you structured your code in a sensible way, you need to scan an additional package. Your application may resemble the following code:
#SpringBootApplication
#ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {
// ...
}
Doing so effectively overrides the default component scan directive with the side effect of scanning those two packages regardless of the slice that you chose. For instance, a #DataJpaTest seems to suddenly scan components and user configurations of your application. Again, moving the custom directive to a separate class is a good way to fix this issue.
One solution is to create a seperate #Configuration that is annotated with #ComponentScan. When creating a #WebMvcTest the configuration (and its component scan is ignored).
#Configuration
#ComponentScan("com.example.another")
public class DbConfig {
}
This usually happens when you have explicit #ComponentScan annotation on the spring boot main application class.
The #ComponentScan annotation suppresses the default component scanning mechanism that happens with #Webmvctest where it scans up the package hierarchy and apply excludeFilters to find only controller and its related classes.
When you mock your bean using #MockBean annotation you should define what that mocked bean should do when you call it's method, you usually do this using when in Mockito. In your case it can be done this way:
#ActiveProfiles("test")
#WebMvcTest
class MyControllerTest {
#MockBean
MyControllerDependency dependency;
#Autowired
MockMvc mockMvc;
#Test
void test_something() throws Exception {
when(dependency.sample()).thenReturn("Hello, Mock");
mockMvc.perform(get("/api/test/restpoint")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk());
}
}
At this line:
when(dependency.sample()).thenReturn("Hello, Mock");
Instead of dependency.sample() you should put whatever method of MyControllerDependency class your controller call when you send a GET request to /api/test/restpoint path and with thenReturn("Hello, Mock") you define what is the mocked output of that method (when it gets called by your controller in your Unit test).
I am using a dependent module called spring-cloud-aws. It has a #Configuration class as org.springframework.cloud.aws.messaging.config.annotation.SqsConfiguration
In my SpringBoot JUnit test case the SqsConfiguration class is getting detected and Beans are getting initialized. I want to exclude this Configuration in class in my JUNit test case. How to achieve this ?
I tried using #ComponentScan it didn't work.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = SQLTestConfig.class)
#ActiveProfiles("test")
public class BusinessManagerTest {
}
#TestConfiguration
#ComponentScan(basePackages = {"package1","package1"},
excludeFilters = {#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = SqsConfiguration.class)})
#Profile("test")
class SQLTestConfig {
#Bean
public SomeBean beans() {
return new SomeBean();
}
}
Loading this configuration class requires aws credentials to be available. I don't want to inject credentials for running a simple Bean test case.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpleMessageListenerContainer' defined in class path resource [org/springframework/cloud/aws/messaging/config/annotation/SqsConfiguration.class]: Invocation of init method failed; nested exception is com.amazonaws.services.sqs.model.AmazonSQSException: The security token included in the request is expired
There are multiple ways to exclude specific auto-configuration during testing:
exclude via properties in your application-test.properties
spring.autoconfigure.exclude=org.springframework.cloud.aws.messaging.config.annotation.SqsConfiguration
exclude via #TestPropertySource:
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#SpringBootTest(classes = SQLTestConfig.class)
#TestPropertySource(properties ="spring.autoconfigure.exclude=org.springframework.cloud.aws.messaging.config.annotation.SqsConfiguration")
exclude via #EnableAutoConfiguration, e.g.:
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#SpringBootTest(classes = SQLTestConfig.class)
#EnableAutoConfiguration(exclude=SqsConfiguration.class)
Choose one that suites you better ;)
So to disable the auto-loading of all Beans for a Test, the test class can explicitly mention the dependencies required. This can be done using ContextConfiguration annotation. eg,
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {EmployeeService.class})
public class EmployeeLeavesTest {
#Autowired
private EmployeeService employeeService;
}
In this eg, only EmployeeService class will be available and other beans will not be loaded.
PIDClient has HttpClient (Java 11) as a dependency in its constructor. I want to autowire this so I created an #Configuration annotated class named SpringConfiguration. I got it working with the code below.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {PIDClient.class, SpringConfiguration.class})
public class PIDClientTest {
My issue with this is, now it loads all the beans in SpringConfiguration while I only need HttpClient bean. I tried the code below but that gave me a No qualifying bean of type 'java.net.http.HttpClient'.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {PIDClient.class, HttpClient.class})
public class PIDClientTest {
Here's a screenshot of the projects' directory structure in case it's needed with the relevant classes open.
Is there a better way?