How to run tests using a custom WebApplicationInitializer? - spring

class MyWebAppInitializer extends WebApplicationInitializer {
def onStartup(servletContext: ServletContext): Unit = {
...
}
}
#RunWith(classOf[SpringJUnit4ClassRunner])
#WebAppConfiguration
#ContextConfiguration(classes = Array(classOf[MyConfig]),
initializers=Array(classOf[MyWebAppInitializer])) // <<< ERROR
class MyTest {
...
}
Complains about :
annotation argument needs to be a constant; found: classOf[MyWebAppInitializer]
UPDATE: #M. Deinum points out that only ApplicationContextInitializers are allowed here - so the error is a badly reported type mistmatch.
So... how can I use my own MyWebAppInitializer in order and test the functionality defined therein?

In your context configuration I don't see that you have a context loader listed. The AnnotationConfigWebContextLoader will locate instances of WebApplicationInitializer on your classpath, by adding this and removing the intializers (which, as you have noted, are for ApplicationContextInitializers and not WebApplicationInitializers) then you should be all set.
#RunWith(classOf[SpringJUnit4ClassRunner])
#WebAppConfiguration
#ContextConfiguration(classes = {ConfigClass.class, AnotherConfigClass.class}, loader=AnnotationConfigWebContextLoader.class))
class MyTest {
...
Here is a working example
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes={WebConfiguration.class, SecurityConfig.class}, loader=AnnotationConfigWebContextLoader.class)
#ActiveProfiles("dev")
public class AppTests {
private MockMvc mockMvc;
#Autowired
protected WebApplicationContext webApplicationContext;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void simple() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.view().name("index"));
}
}

The answer is "Sorry,you can't".
You could refer to this:
Spring FrameworkSPR-10199
Add capability to use WebApplicationInitializer for testing Spring web applications
Just as Sam Brannen said:
Although Spring does provide mocks for the Servlet API, Spring does
not mock a Servlet container and currently has no intention to. Spring
has always focused on out of container integration testing. Fully
mocking a container is therefore beyond the scope of Spring's testing
support. Please see comments from Rossen and me above for further
details.

Related

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 .

Is there a way to include a spring component in a WebMvcTest

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
}

Spring Boot - Test for controller fails with 404 code

I want to write a test for controller. Here is test snippet:
#RunWith(SpringRunner.class)
#WebMvcTest(WeatherStationController.class)
#ContextConfiguration(classes = MockConfig.class)
public class WeatherStationControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private IStationRepository stationRepository;
#Test
public void shouldReturnCorrectStation() throws Exception {
mockMvc.perform(get("/stations")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
controller code snippet:
#RestController
#RequestMapping(value = "stations")
public class WeatherStationController {
#Autowired
private WeatherStationService weatherService;
#RequestMapping(method = RequestMethod.GET)
public List<WeatherStation> getAllWeatherStations() {
return weatherService.getAllStations();
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public WeatherStation getWeatherStation(#PathVariable String id) {
return weatherService.getStation(id);
}
MockConfig class:
#Configuration
#ComponentScan(basePackages = "edu.lelyak.repository")
public class MockConfig {
//**************************** MOCK BEANS ******************************
#Bean
#Primary
public WeatherStationService weatherServiceMock() {
WeatherStationService mock = Mockito.mock(WeatherStationService.class);
return mock;
}
Here is error stack trace:
java.lang.AssertionError: Status
Expected :200
Actual :404
I can get what is wrong here.
How to fix test for controller?
HTTP code 404, means no resource found (on the server) for your request, which I think that your controller is not visible(let me say is not scanned) by spring boot.
A simple solution is scanning a parent package in MockConfig class, so spring can pick up all beans,
#ComponentScan(basePackages = "edu.lelyak") // assuming that's the parent package in your project
if you don't like this approach, you can add the controller's package name in basePackages
#ComponentScan(basePackages = {"edu.lelyak.controller","edu.lelyak.repository")
BTW, you don't have to manually set up WeatherStationService in MockConfig class, Spring boot can inject a mock for you and automatically reset it after each test method, you should just declare it in your test class:
#MockBean
private IStationRepository stationRepository;
On the other hand, you should mock weatherService.getAllStations() before calling get("/stations") in your test method (as you're not running integration test), so you can do:
List<WeatherStation> myList = ...;
//Add element(s) to your list
Mockito.when(stationService.getAllStations()).thenReturn(myList);
You can find more in :
Testing improvements in Spring Boot 1.4
Spring Boot features: Testing
I had the same issue. The controller was not getting picked up despite specifying it with #WebMvcTest(MyController.class). This meant all of its mappings were ignored, causing the 404. Adding #Import(MyController.class) resolved the issue, but I didn't expect the import to be necessary when I'm already specifying which controller to test.
I am not sure why your test is not working. But I got another solution which works for me.
#SpringBootTest
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new TestController()).build();
}
#Test
public void shouldReturnCorrectStation() throws Exception {
mockMvc.perform(get("/stations")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
After some debugging, it appears that the target controller is simply not registered as a method handler. Spring scans the beans for the presence of RestController annotation.
But the problem is that the annotation could be found only if the bean is proxied via CGLIB, but for the case when we use WebMvcTest it's proxied by JDK.
As a result, I searched for the configuration which is responsible for making the choice, and the one was finally found AopAutoConfiguration. So when SpringBootTest is used this one is autoloaded when you need WebMvcTest+PreAuthorize in your controllers, then simply use:
#Import(AopAutoConfiguration.class)
I import external configuration class by #ContextConfiguration(classes = MyConfig.class)
When I changed in MyConfig annotation #Configuration into #TestConfiguration it started to work properly.
I couldn't find a good answer but I could find one of the causes.
I was using in my tests the #PreAuthorize on the RestController.
You can mock the Oauth with this tip on the integration tests that use SpringBootTest. For SpringBootTest, this works very well too, but using SpringBootTest you load a lot of other resources (like JPA) that is not necessary to do a simple Controller test.
But with #WebMvcTest this not works as expected. The use of the WithMockOAuth2Scope annotation can be enough to stop the 401 error from authentication problem, but after that the WebMvcTest can't find the rest endpoint, returning the 404 error code.
After removing the #PreAuthorize on Controller, the test with WebMvcTest pass.
Based on the accepted answer, in my case I had copied and modified the file based on another test, but forgot to change the name for the controller on the top of the class, that being the reason why it was not finding the resource, as the error says.
#RunWith(SpringRunner.class)
#WebMvcTest(AnswerCommandController.class)
public class AnswerCommandControllerTest {
Here is a different approach to the controller test that worked for me.
Assumption: The class WeatherStationService is a #SpringBootApplication
Then, the test class below should work for you:
#RunWith(SpringRunner.class)
#SpringApplicationConfiguration(WeatherStationService.class)
#WebIntegrationTest
public class WeatherStationControllerTest {
#Autowired
private WebApplicationContext context;
MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
#Test
public void shouldReturnCorrectStation() throws Exception {
mockMvc.perform(get("/stations")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk();
}
}
With this test setup, you should no longer need the MockConfig class.
In my case it was about a missing starting slash /
I've appended / to both RequestMapping value and MockHttpServletRequestBuilder post urlTemplate parameters as first character.
In case anyone is wondering.
If we don't use #ContextConfiguration, #WebMvcTest annotation will load the REST controller class. Otherwise, when we use use #ContextConfiguration, seems ContextConfiguration clear the context REST controller config. We need to add the REST controller to ContextConfiguration such as:
#ContextConfiguration(classes = {MockConfig.class, WeatherStationController.class})

Spring HATEOAS Resource assembler is not instantiated in unit test

I am trying to write a unit test for a REST controller which generates HATEOAS links via Resource assembler class. Everything is OK in production, but with the unit test Resource assembler class is not being injected into the controller.
my resource assembler class is:
#Component
public class ModelResourceAssembler extends ResourceAssemblerSupport<Model, ModelResource> {
public ModelResourceAssembler() {
super(ModelRestController.class, ModelResource.class);
}
#Bean
public ModelResourceAssembler modelResourceAssembler(){
return new ModelResourceAssembler();
}
#Override
public ModelResource toResource(Model model) {
...
}
}
The controller is:
#Controller
#RequestMapping("/demo")
#ComponentScan(basePackages = {"com.foo.demo"} )
public class ModelRestController {
#Autowired
private ModelPersistenceHandler modelPersistenceHandler;
#Autowired
private ModelResourceAssembler modelResourceAssembler;
...
}
And the unit test:
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes= {ModelResourceAssembler.class, ModelRestController.class})
public class ModelRestControllerTest {
private MockMvc mockMvc;
#InjectMocks
private ModelRestController modelRestController;
#Mock
private ModelPersistenceHandler modelPersistenceHandler;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(modelRestController).build();
}
...
}
No matter what I do the ModelResourceAssembler instance is always null. Since the application is Spring Boot it does not have the WebCoonfig classes and autowired WebApplicationContext is always null, so I cannot (and really don't want to since I am running a unit test) instantiate MockMvc via webAppContextSetup
The solution ended up being quite simple: I needed to add one line to my test:
#Spy
private ModelResourceAssembler modelResourceAssembler;
And the bean was instantiated and properly wired
In your example you use #InjectMocks but don't declare a mock for ModelResourceAssembler. You don't get an instance out of nowhere.
You use the MockitoJUnitRunner.class. It has no idea of Spring beans. For testing Spring applications you rather want to use SpringJUnit4ClassRunner.class.
If i may suggest, if you use constructor injection for your controller then you can just mock the dependency and not need spring junit test runner stuff.

Spring test #ContextConfiguration and static context

I have the following piece of code for my abstract test class (I know XmlBeanFactory with ClassPathResource is deprecated, but it's unlikely to be the case of the problem).
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public abstract class AbstractIntegrationTest {
/** Spring context. */
protected static final BeanFactory context = new XmlBeanFactory(new ClassPathResource(
"com/.../AbstractIntegrationTest-context.xml"));
...
}
It loads the default test configuration XML file AbstractIntegrationTest-context.xml (and then I use autowiring). I also need to use Spring in static methods annotated with #BeforeClass and #AfterClass, so I have a separate context variable pointing to the same location. But the thing is that this is a separate context, which will have different instances of beans. So how can I merge these contexts or how can I invoke Spring's bean initialization defined by #ContextConfiguration from my static context?
I have in mind a possible solution by getting rid of those static members, but I'm curious, if I can do it with relatively small changes to the code.
You are right, your code will produce two application contexts: one will be started, cached and maintained for you by #ContextConfiguration annotation. The second context you create yourself. It doesn't make much sense to have both.
Unfortunately JUnit is not very well suited for integration tests - mainly because you cannot have before class and after class non-static methods. I see two choices for you:
switch to testng - I know it's a big step
encode your setup/tear down logic in a Spring bean included in the context only during tests - but then it will run only once, before all tests.
There are also less elegant approaches. You can use static variable and inject context to it:
private static ApplicationContext context;
#AfterClass
public static afterClass() {
//here context is accessible
}
#Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
Or you can annotate your test class with #DirtiesContext and do the cleanup in some test bean:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#DirtiesContext(classMode = AFTER_CLASS)
public abstract class AbstractIntegrationTest {
//...
}
public class OnlyForTestsBean {
#PreDestroy
public void willBeCalledAfterEachTestClassDuringShutdown() {
//..
}
}
Not sure whether you chose any approach here, but I encounter the same problem and solved it another way using Spring test framework's TestExecutionListener.
There are beforeTestClass and afterTestClass, so both equivalent to #BeforeClass and #AfterClass in JUnit.
The way I do it:
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners(Cleanup.class)
#ContextConfiguration(locations = { "/integrationtest/rest_test_app_ctx.xml" })
public abstract class AbstractIntegrationTest {
// Start server for integration test.
}
You need to create a class that extends AbstractTestExecutionListener:
public class Cleanup extends AbstractTestExecutionListener
{
#Override
public void afterTestClass(TestContext testContext) throws Exception
{
System.out.println("cleaning up now");
DomainService domainService=(DomainService)testContext.getApplicationContext().getBean("domainService");
domainService.delete();
}
}
By doing this, you have access to the application context and do your setup/teardown here with spring beans.
Hopefully this help anyone trying to do integration test like me using JUnit + Spring.

Resources