I have a library and I want to implement an integrations test for the Repository (only an example for a more complex case).
To reproduce the case, I used this official example, but removing the AccessingDataJpaApplication.java class because I will expose it as a library and not as application.
Now that the repo does not have the #SpringBootApplication annotation, the test will fail because can not inject the private CustomerRepository customers;
How to reproduce the case:
git clone https://github.com/spring-guides/gs-accessing-data-jpa.git
cd gs-accessing-data-jpa/complete
mvn test # Working
rm src/main/java/com/example/accessingdatajpa/AccessingDataJpaApplication.java
mvn test # not working
The question is, which is the right way to annotate this test if it is not an application?
I tried annotating the test with few combinations like:
Adding #SpringBootTest:
#ExtendWith(SpringExtension.class)
#DataJpaTest
#SpringBootTest
public class CustomerRepositoryTests {
}
The error looks like try to say that there are multiple inizializations, I suppose that each annotation try to do its own:
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.067 s <<< FAILURE! - in com.example.accessingdatajpa.CustomerRepositoryTests
[ERROR] com.example.accessingdatajpa.CustomerRepositoryTests Time elapsed: 0.067 s <<< ERROR!
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class [com.example.accessingdatajpa.CustomerRepositoryTests]: [#org.springframework.test.context.BootstrapWith(value=org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper.class), #org.springframework.test.context.BootstrapWith(value=org.springframework.boot.test.context.SpringBootTestContextBootstrapper.class)]
Setting only #SpringBootTest
#ExtendWith(SpringExtension.class)
#SpringBootTest
public class CustomerRepositoryTests {}
Then I can not use the TestEntityManager and still is not able to inject the repository
17:29:01.951 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither #ContextConfiguration nor #ContextHierarchy found for test class [CustomerRepositoryTests]: using SpringBootContextLoader
17:29:01.954 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.example.accessingdatajpa.CustomerRepositoryTests]: no resource found for suffixes {-context.xml, Context.groovy}.
17:29:01.954 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.example.accessingdatajpa.CustomerRepositoryTests]: CustomerRepositoryTests does not declare any static, non-private, non-final, nested classes annotated with #Configuration.
17:29:01.964 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using ContextCustomizers for test class [CustomerRepositoryTests]: [ExcludeFilterContextCustomizer, DuplicateJsonObjectContextCustomizer, MockitoContextCustomizer, TestRestTemplateContextCustomizer, DisableObservabilityContextCustomizer, PropertyMappingContextCustomizer, Customizer]
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.117 s <<< FAILURE! - in com.example.accessingdatajpa.CustomerRepositoryTests
[ERROR] com.example.accessingdatajpa.CustomerRepositoryTests Time elapsed: 0.116 s <<< ERROR!
java.lang.NullPointerException: Cannot invoke "java.lang.Class.getName()" because "found" is null
And #SpringBootTest(classes = {CustomerRepository.class}):
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {CustomerRepository.class})
public class CustomerRepositoryTests {}
With error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.accessingdatajpa.CustomerRepositoryTests': Unsatisfied dependency expressed through field 'customers': No qualifying bean of type 'com.example.accessingdatajpa.CustomerRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Maybe the solution is related to [com.example.accessingdatajpa.CustomerRepositoryTests]: CustomerRepositoryTests does not declare any static, non-private, non-final, nested classes annotated with #Configuration.. But how to do it exactly?
BTW: I found this response in Stackoverflow, but it is not working. Maybe because the Spring version.
This is the working approach:
#ExtendWith(SpringExtension.class)
#DataJpaTest
public class CustomerRepositoryTests {
#SpringBootApplication
static class TestConfig {}
#Autowired
private TestEntityManager entityManager;
#Autowired
private CustomerRepository customers;
#Test
public void testFindByLastName() {
......
}
}
So each test class will autoconfigure the context using Spring Boot.
Reference: spring-data-examples repository
Related
In that class I add the #TestPropertySource:
#TestConfig
#TestInstance(PER_CLASS)
#TestPropertySource(properties = {"name=BigSam"})
class Test5 {
//...tests
}
I have 5 integration tests. The common configuration for tests:
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#SpringBootTest(classes = MainStart.class)
#ActiveProfiles("test")
public #interface TestConfig {}
If I run the Test5 separately it works fine. But, when I run all integration tests together including Test5 class, I see in the logs:
" The following 1 profile is active "test" " - more than one time. ( If I run an integration test without Test5 class, this message appears only one time)
And I the errors:
java.lang.IllegalStateException: Failed to load ApplicationContext
javax.cache.CacheException: A Cache named "cache-name" already exists.
How can the configuration of the test be fixed?
Or how can I change the property source and don't get such errors?
p.s. I use cache from Hibernate, but I don't think that it is a problem.
I have following test:
#SpringBootTest(classes = {SomeService.class, DtoMapperImpl.class})
class SomeServiceTest {
And following mapper:
#Mapper(componentModel = "spring")
public interface DtoMapper {
EntityDto toDto(Entity entity);
}
I'm not changing packages (this means DtoMapperImpl is in the same package as DtoMapper)
As soon as I change Impl to interface my test fails:
#SpringBootTest(classes = {SomeService.class, DtoMapper.class})
class SomeServiceTest {
Caused by:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'someService': Unsatisfied dependency
expressed through constructor parameter 2; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type 'DtoMapper' available: expected at least 1
bean which qualifies as autowire candidate. Dependency annotations: {}
Can you please advise best way solving this? I'm on MapStruct 1.3.1.Final
The problem actually has nothing to do with MapStruct, but rather to how SpringBootTest#classes is used.
The classes in SpringBootTest is meant to provide your components that should be used to load in the test.
From the JavaDoc:
The component classes to use for loading an ApplicationContext. Can also be specified using #ContextConfiguration(classes=...). If no explicit classes are defined the test will look for nested #Configuration classes, before falling back to a #SpringBootConfiguration search.
Returns:
the component classes used to load the application context
In your case you have 2 classes:
SomeService - which I assume is a class annotated with #Service and Spring will correctly load it
DtoMapper - this is the MapStruct mapper which is an interface and it isn't a component. The component which you want for your tests is DtoMapperImpl
You have several options to fix this:
Use the Impl class
You can use the DtoMapperImpl (the Spring Component class) in your SpringBootTest#classes, your test will then load the correct component
Use a custom configuration class that will component scan your mappers
#TestConfiguration
#ComponentScan("com.example.mapper")
public class MappersConfig {
}
And then use this in your SpringBootTest#classes. E.g.
#SpringBootTest(classes = {SomeService.class, MappersConfig.class})
class SomeServiceTest {
...
}
Create following configuration (should point where mappers are):
#TestConfiguration
#ComponentScan("some.package.mapper")
public class MappersConfig {
}
And modify slice:
#SpringBootTest(classes = {SomeService.class, MappersConfig.class})
class SomeServiceTest {
I would suggest a little improvement for accepted answer so that you don't have to write package name as hard-coded string, but use ComponentScan basePackageClasses instead:
#TestConfiguration
// #ComponentScan("some.package.mapper")
#ComponentScan(basePackageClasses = DtoMapper.class)
public class MappersConfig {
}
But there is an obvious drawback in this (as well as accepted answer) approach: the whole package will be scanned which may contain undesired classes.
I have this test class :
#RunWith(SpringRunner.class)
#WebMvcTest(ClassToBeTested.class)
public class ClassToBeTestedTests {
#Autowired
private MockMvc mockMvc;
#Test
public void simpleTestMethodToGetClassWorking(){
Assert.assertTrue(true);
}
}
but in the class I want to test, I have this line :
#Autowired
AnnoyingServiceWhichIsADependency annoyingDependency;
So when I try to run the test class - I get this error :
java.lang.IllegalStateException: Failed to load ApplicationContext
and the cause by line seems to throw this up :
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'ClassToBeTested': Unsatisfied dependency expressed through field 'AnnoyingServiceWhichIsADependency'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type '<package-path>.AnnoyingServiceWhichIsADependency' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I will add that the actual class does work, and does what it is meant to do, but I am having trouble making it work in the unit test world.
All help appreciated.
The reason a bean is not created for the dependency class is that you're using #WebMvcTest and not #SpringBootTest: only controllers and the MVC infrastructure classes are scanned. From the docs:
Can be used when a test focuses only on Spring MVC components.
Since it's an MVC test, you can mock the service dependency.
Example: https://reflectoring.io/spring-boot-web-controller-test/
#WebMvcTest is only going to scan the web layer- the MVC infrastructure and #Controller classes. That's it. So if your controller has some dependency to other beans from, e.g. form your service layer, they won't be found to be injected.
If you want a more comprehensive integration test, use #SpringBootTest instead of #WebMvcTest
If you want something closer to a unit test, mock your dependency.
Also note that Field injection (#Autowired directly on the field) is not recommended exactly for these reasons. I recommend you change to constructor injeciton ( add a constructor for Classtobetested and place the #Autowired annotation on it. ) Then for a unit test you can pass in a mock. Constructor injection leads to a more testable and configurable design.
Your test application context is trying to load your ClassToBeTested but is unable to find one of its dependencies and complains about it via that error. Basically you need to have a #Bean of that type in your test context. An option will be to create a TestConfig class which provides a Mock/Spy of that dependency via #Bean annotation. In your test you will have to load inside the context via the #ContextConfiguration annotation this test config you just created.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#spring-testing-annotation-contextconfiguration
Just mock that dependency. Assuming that AnnoyingServiceWhichIsADependency is an interface:
#RunWith(SpringRunner.class)
#WebMvcTest(ClassToBeTested.class)
public class ClassToBeTestedTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private AnnoyingServiceWhichIsADependency annoyingDependency;
#Test
public void simpleTestMethodToGetClassWorking(){
Assert.assertTrue(true);
}
}
Use Mockito when and thenReturn methods to instruct the mock.
Here's my test case for a Spring Controller
#RunWith(SpringRunner.class)
#WebMvcTest(value = MyController.class)
public class MyControllerTest {
#MockBean
private MyService myService;
}
So this is a unit test specifically for the methods in MyController. But when I run the test, Spring appears to begin instantiating OtherController and all of it's dependencies.
I have tried updating the above as
#RunWith(SpringRunner.class)
#WebMvcTest(value = MyController.class, excludeFilters = #ComponentScan.Filter(value= OtherController.class, type = FilterType.ANNOTATION))
public class MyControllerTest {
...
}
But spring still appears to wire it. Here's the error thrown by Spring as it tries to instantiate OtherController when I run the above test specifically.
2017-01-06 12:09:46.207 WARN 18092 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'otherController' defined in file [C:\..OtherController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'getOtherService' defined in com.my.myApplication: Unsatisfied dependency expressed through method 'getOtherService' parameter 0org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'getOtherService' defined in com.myOrg.MyServiceApplication: Unsatisfied dependency expressed through method 'getPositionService' parameter 0
What could be causing this?
Chances are that you are triggering the bean scanning by accident via a #Componentscan or the likes.
For instance, as explained in this answer, Spring may be detecting your "production" #SpringBootApplication Application class, with all the component scans it brings about. If so make sure you "overwrite" your "production" Application class with your "test" one putting...
#SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
...in a location that would override the "Production" Application.
For instance, if your IDE doesn't get messed by class name clashes:
"production" -> src/main/java/com/mycompany/Application.java
-> src/main/java/com/mycompany/controllers/MyController.java
"test" -> src/test/java/com/mycompany/Application.java
-> src/test/java/com/mycompany/controllers/MyControllerTest.java
As an alternative I also have found that in my case this works as well (i.e. placing the Application in the test controllers folder)
"production" -> src/main/java/com/mycompany/Application.java
-> src/main/java/com/mycompany/controllers/MyController.java
"test" -> src/test/java/com/mycompany/controllers/Application.java
-> src/test/java/com/mycompany/controllers/MyControllerTest.java
Initially I was using only Mockito in junits so I was using SpringJUnit4ClassRunner.class in #RunWith annotation ie
#RunWith(SpringJUnit4ClassRunner.class)
due to which spring dependency injection was working fine and was getting a bean through
#Autowired
Someservice someservice ;
But now, I have also integrated PowerMock in it.
So as per doc , I have replaced class mentioned in #RunWith annotation with
#RunWith(PowerMockRunner.class)
but now, someservice is coming out to be null. Is there a way to use both SpringJUnit4ClassRunner.class and PowerMockRunner.class in #RunWith annotation
I know this thread is old, but it's good to add that since 2014 and this pull request, you can use the #PowerMockRunnerDelegate annotation to "delegate" the run context to SpringJUnit4ClassRunner (or any other runner really).
Above code would look like :
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
#PrepareForTest(X.class);
public class MyTest {
// Tests goes here
...
}
With this annotation, you don't need the PowerMock rule anymore !
You have to use the PowerMockRule.
#RunWith(SpringJUnit4ClassRunner.class)
#PrepareForTest(X.class)
public class MyTest {
#Rule
public PowerMockRule rule = new PowerMockRule();
// Tests goes here
...
}
For a full example of the Spring Integration Test with PowerMock and Mockito, you could checkout this maven project.
svn co http://powermock.googlecode.com/svn/tags/powermock-1.4.12/examples/spring-mockito/
cd spring-mockito/
Look at the dependecies to powermock.
less pom.xml
and then run the test
mvn test
and you should get the following test results :
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0