I'm refactoring spring boot tests creating encapsulated classes with common behavior that will be injected in other tests. This classes has scope only in test package.
Spring ignore slices that will not be used in test (which is great and by design of spring boot test 1.5), but also ignore any #Component in src/test/java.
Question is how to configure spring boot test to pickup components in test/java?
I have one incomplete solution, that works for one test
My current solution is:
import com.example.testClasses.TestUtil;
#RunWith(SpringRunner.class)
#SpringBootTest
#Import(TestConfiguration.class)
public class ExampleTest {
#SpyBean
private ServiceDependency1 service1;
#Autowired
private TestUtil testUtil;
}
#Configuration
#ComponentScan(basePackages = "com.example.testClasses")
public class TestConfiguration {
}
#Component
public class TestUtil {
public TestUtil(ServiceDependency1 service) {
}
}
The solution above partial works, when another Utils is added TestUtils2 with different injection dependencies, this dependencies are not resolved.
That is because dependency for TestUtil1 is only solved with the #SpyBean, this is not the case in second test.
I put the all SpyBean on the TestConfiguration and use #AutoWired for each test.
import com.example.testClasses.TestUtil;
#RunWith(SpringRunner.class)
#SpringBootTest
#Import(TestConfiguration.class)
public class ExampleTest {
#Autowired
private ServiceDependency1 service1;
#Autowired
private TestUtil testUtil;
}
#Configuration
#ComponentScan(basePackages = "com.example.testClasses")
public class TestConfiguration {
#SpyBean
private ServiceDependency1 service1
#SpyBean
private ServiceDependency2 service2
}
#Component
public class TestUtil {
public TestUtil(ServiceDependency1 service) {
}
}
Related
For some time I've been struggling to make JUnit tests for my rest controller. For some reason, every time I try to run them I get the error Status expected:<200> but was:<404>. Here is my controller:
#RestController
#RequestMapping("/travels")
#RequiredArgsConstructor
public class TravelController {
private final TravelService travelService;
private final TravelOutputDtoMapper travelOutputDtoMapper;
#GetMapping
public List<TravelOutputDto> getAll() {
List<Travel> travels = travelService.getAll();
return travels.stream()
.map(travelOutputDtoMapper::travelToTravelOutputDto)
.collect(Collectors.toList());
}
}
And here is my test:
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = TravelController.class)
#ContextConfiguration(classes = {
TravelOutputDtoMapper.class,
TravelOutputDtoMapperImpl.class
})
class TravelControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TravelService travelService;
#Autowired
private TravelOutputDtoMapper travelOutputDtoMapper;
#Test
void testGetAll() throws Exception {
List<Travel> travels = mockTravelList();
Mockito.when(travelService.getAll()).thenReturn(travels);
mockMvc.perform(get("/travels"))
.andExpect(status().isOk());
}
private List<Travel> mockTravelList() {
// Dummy travel list
}
}
I think the reason is connected with TravelOutputDtoMapper as if I remove it from the controller and don't try to inject it the tests are passing, but I cannot find any information why it is doing it. The autowired mapper has an instance and works just fine.
Here is the Mapper:
#Mapper(componentModel = "spring")
public interface TravelOutputDtoMapper {
#Mapping(target = "from", source = "entity.from.code")
#Mapping(target = "to", source = "entity.to.code")
TravelOutputDto travelToTravelOutputDto(Travel entity);
}
The #ContextConfiguration annotation is used for a different purpose:
#ContextConfiguration defines class-level metadata that is used to determine how to load and configure an ApplicationContext for integration tests.
Using Spring Boot and #WebMvcTest there's no need to manually specify how to load the context. That's done for you in the background.
If you'd use this annotation, you'd specify your main Spring Boot class here (your entry-point class with the #SpringBootApplication annotation).
From what I can see in your test and your question is that you want to provide an actual bean for the TravelOutputDtoMapper, but mock the TravelService.
In this case, you can use #TestConfiguration to add further beans to your sliced Spring TestContext:
// #ExtendWith(SpringExtension.class) can be removed. This extension is already registered with #WebMvcTest
#WebMvcTest(controllers = TravelController.class)
class TravelControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TravelService travelService;
#Autowired
private TravelOutputDtoMapper travelOutputDtoMapper;
#TestConfiguration
static class TestConfig {
#Bean
public TravelOutputDtoMapper travelOutputDtoMapper() {
return new TravelOutputDtoMapper(); // I assume your mapper has no collaborators
}
}
// ... your MockMvc tests
}
I have a Controller just for tests with dummy apis
#RestController
public class TestController {
#Autowired
private org.springframework.cloud.sleuth.Tracer tracer;
#GetMapping("/trace")
public Mono<String> traceTest() {
...
}
}
Here's my test
#WebFluxTest(controllers = TestController.class)
public MyTest {
#Autowired
private WebTestClient webClient;
#Test
public void testTrace() {
webClient.get().uri("/trace")...
}
}
When I try to run this, my Tracer is not Autowired.
Of course, if I change my test to a #SpringBootTest, it all works
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public MyTest {
...
}
But I'd like to avoid autowiring my entire spring application. How can I get TestController to be auto-configured?
The #WebFluxTest annotation populates a Spring TestContext with only a subset of the relevant beans:
#WebFluxTest auto-configures the Spring WebFlux infrastructure and
limits scanned beans to #Controller, #ControllerAdvice,
#JsonComponent, Converter, GenericConverter, WebFilter, and
WebFluxConfigurer. Regular #Component and #ConfigurationProperties
beans are not scanned when the #WebFluxTest annotation is used.
#EnableConfigurationProperties can be used to include
#ConfigurationProperties beans. from Spring Boot Documentation
Your Tracer won't be part of this TestContext out-of-the-box.
For your #WebFluxTest you can provide a mocked version of this bean with #MockBean:
#WebFluxTest(controllers = TestController.class)
public MyTest {
#Autowired
private WebTestClient webClient;
#MockBean
private Tracer tracer;
#Test
public void testTrace() {
webClient.get().uri("/trace")...
}
}
... and if you want to test the full integration I would rather use #SpringBootTest.
Given production code classes:
#RestController
#RequiredArgsConstructor
public class MyController {
private final MyValidator validator;
// annotations relating to request mapping excluded for brevity
public void test(#Valid #RequestBody final MyParams params) {
// do stuff
}
#InitBinder
#SuppressWarnings("unused")
protected void initBinder(final WebDataBinder binder) {
binder.setValidator(validator);
}
}
and
#Component
#RequiredArgsConstructor
public class MyValidator implements Validator {
...
#Override
public void validate(final Object target, final Errors errors) {
// custom validation
}
}
and finally test code:
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
// tests
}
I encounter the error:
NoSuchBeanDefinitionException: No qualifying bean of type 'MyValidator' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I think the error is fair enough. I've annotated the test as a WebMvcTest, which I believe has excluded #Component beans. This is intentional and desired (from the perspective that I am only wanting to test the "web layer", not the whole context - it just so happens I need a component which is related/used only in the controllers)
My question, therefore, is: how can one explicitly include a component like a validator in the test context for a web test?
My environment is java version "10.0.2" 2018-07-17, spring boot 1.5.16.RELEASE.
There are two ways to solve this.
Using #SpringBootTest and #AutoConfigureMvc instead of #RunWith(SpringRunner.class) and #WebMvcTest.
#SpringBootTest
#AutoConfigureMvc
public class MyControllerTest {
}
Creating a #TestConfiguration class that injects the 'MyValidator' bean as:
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
#TestConfiguration
static class TestConfig {
#Bean
MyValidator getMyValidator(){
return new MyValidator();
}
}
// tests
}
More on this can be found here : https://mkyong.com/spring-boot/spring-boot-how-to-init-a-bean-for-testing/
There are two ways to test the web layer
first.
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
private MyController myController;
}
The #SpringBootTest annotation tells Spring Boot to go and look for a
main configuration class (one with #SpringBootApplication for
instance), and use that to start a Spring application context.
A nice feature of the Spring Test support is that the application
context is cached in between tests, so if you have multiple methods in
a test case, or multiple test cases with the same configuration, they
only incur the cost of starting the application once. You can control
the cache using the #DirtiesContext annotation.
Secondly, if you want to use the #WebMvcTest(MyController.class)
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
#MockBean
private MyValidator validator;
}
But this validator is a fake, so you have to customize it for testing.
See this link for more details https://spring.io/guides/gs/testing-web/
I cannot recommend it as a standard practice but if you do need an instance of a dependency in your Web MVC tests (for example in legacy code) you can add them into the spring context using #SpyBean annotation.
Real methods of that class will be called during the test and you can verify them if needed similarly to the beans annotated with #MockBean
#RunWith(SpringRunner.class)
#WebMvcTest(MyController.class)
public class MyControllerTest {
#SpyBean
private MyValidator validator
}
I added a junit test to a simple spring example but it fails to autowire the json service that I wrote.
What is needed to get autowiring to work in a spring JUnit tests?
To try the failing project out do ...
git clone https://bitbucket.org/oakstair/spring-boot-cucumber-example
cd spring-boot-cucumber-example
./gradlew test
Thanks in advance!
Application
#SpringBootApplication
#ComponentScan("demo")
public class DemoApplication extends SpringBootServletInitializer {
Service interface
#Service
public interface JsonUtils {
<T> T fromJson(String json, Class<T> clazz);
String toJson(Object object);
}
Service implementation
#Component
public class JsonUtilsJacksonImpl implements JsonUtils {
Test
#ContextConfiguration()
#RunWith(SpringJUnit4ClassRunner.class)
#ComponentScan("demo")
public class JsonUtilsTest {
#Autowired
private JsonUtils jsn;
In your JsonUtilsTest you can't put a #ComponentScan on the class level here since it isn't a #Configuration class. With a #ContextConfiguration annotation like you are using here it is first looking for a static inner #Configuration class so add one of those with the #ComponentScan and it should work:
#ContextConfiguration()
#RunWith(SpringJUnit4ClassRunner.class)
public class JsonUtilsTest {
#Autowired
private JsonUtils jsn;
#Test
// Note: This test is not tested since I haven't got autowiring to work.
public void fromJson() throws Exception {
Integer i = jsn.fromJson("12", Integer.class);
assertEquals(12, (int) i);
}
#Test
// Note: This test is not tested since I haven't got autowiring to work.
public void toJson() throws Exception {
assertEquals("12", jsn.toJson(new Integer(12)));
}
#Configuration
#ComponentScan("demo")
public static class TestConfiguration {
}
}
EDIT: Or you can make Spring boot do the work for you by using the #SpringBootTest annotation with a SpringRunner instead:
#RunWith(SpringRunner.class)
#SpringBootTest
public class JsonUtilsTest {
Adding this to the test class fixed my problems!
#ContextConfiguration(classes = {DemoApplication.class})
Add #SpringBootTest
On your test class
And provide your SpringBootApplication class and Json utils class to the classes field of #SpringBootTest
It should look like this
#ContextConfiguration()
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes={<package>.DemoApplication.class, <package>.JsonUtil.class } )
#ComponentScan("demo")
public class JsonUtilsTest {
So, I'm working on some Spring tests which require dependency injection using annotations:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class BeanTest {
#Autowired
private SomeService someService;
#Configuration
static class ContextConfiguration {
#Bean
public SomeService someService() {
return new SomeService();
}
}
}
I'd really like to not have to repeat this code in every test but my attempts to create a base class which contains the configuration:
#Configuration
class MyContextConfiguration {
#Bean
public SomeService someService() {
return new SomeService();
}
}
And deriving from it:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class BeanTest {
#Autowired
private SomeService someService;
#Configuration
static class ContextConfiguration extends MyContextConfiguration {}
}
Don't seem to work. Can anybody suggest a way to DRY this up?
Thanks!
You should be able to do this instead.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class BeanTest {
#Autowired
private SomeService someService;
#Configuration
#Import(MyContextConfiguration.class)
static class ContextConfiguration {
....
}
}
Also, you don't need to mention AnnotationConfigContextLoader, Spring by convention will automatically pick up the static inner class annotated with #Configuration and use the appropriate ContextLoader
You can declare configuration classes in the the contextconfiguration-annotation. From the documentation.
ContextConfiguration
Defines class-level metadata that is used to determine how to load and configure an ApplicationContext for integration tests. Specifically, #ContextConfiguration declares the application context resource locations or the annotated classes that will be used to load the context.
Resource locations are typically XML configuration files located in the classpath; whereas, annotated classes are typically #Configuration classes. However, resource locations can also refer to files in the file system, and annotated classes can be component classes, etc.
example from the documentation.
#ContextConfiguration(classes = TestConfig.class)
public class ConfigClassApplicationContextTests {
// class body...
}