I'm trying to write some unit tests for my controllers in a Spring MVC web app. I have already got a fairly comprehensive set of unit tests for the domain model, but for completeness I want to test the controllers too.
The issue I'm facing is trying to test them without loading a Spring context. I thought I could get around this with mocking, but the method in the controller has a #Transactional annotation which attempts to open a transaction (and fails with a NullPointerException because no Spring context is loaded).
Code example:
public class UsersController {
#Autowired private UserManager userManager;
#Transactional
#RequestMapping(method = RequestMethod.POST)
public ModelAndView create(User user) {
userManager.save(user);
ModalAndView mav = new ModelAndView();
mav.addObject("user", user);
mav.setViewName("users/view");
return mav;
}
}
So essentially I want to test the behaviour without loading a context and actually persisting the user.
Does anyone have any ideas on how I can achieve this?
Cheers,
Caps
I'd say mocking is the way to go here. The #Transactional annotation will have no effect unless there is a Spring context loaded and instructed to configure annotation-based transactions.
Make sure that you aren't instructing JUnit to run your test within a spring context by specifying something like:
#ContextConfiguration(locations = "classpath:spring/ITestAssembly.xml")
#RunWith(SpringJUnit4ClassRunner.class)
To prevent confusion, I keep my unit tests (not running in a spring context) in separate files than my integration tests. Typically all mocking occurs in the unit tests and not in integration tests.
The NullPointerException occurs not because of the Transactional, but because nothing gets injectedas UserManager. You have two options:
run with the spring test runner
mock the userManager and set it.
Related
I have a Spring 5 web application which does validations only on service level DTOs. That is to say, the incoming request is not validated in controller, but the DTO going into a service layer method is validated on that level. This is a requirement because this is how this is done on other applications and other people want to keep it like that for consistency.
I have tests that use Spring MockMvc to test the controller methods. I'd like to test the error response on validation errors but when I run my tests, bean validation is not done. It works fine when I build and deploy the app, though. Validation also works for tests if I put the validation annotations on the class representing the incoming request and add #Valid to the controller method argument corresponding to the request. It just doesn't work when its done on the service layer.
So my question is is this even supposed to work with MockMvc, that is to say, should validation occur normally when the controller calls the service method? Or is there some configuration I need to have in place for this to work during tests? I doubt it since validation works if I try adding it to controller level...
Here is my test class setup:
#ContextConfiguration(classes = {MyTestConfig.class})
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest {
private MockMvc mvc;
#Autowired
private WebApplicationContext wac;
#Before
public void setup() throws Exception {
this.mvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
The MyTestConfig class just has #EnableWebMvc annotation, base packages for scanning and some unrelated bean definitions.
My DTO has #NotNull annotation on a property, the service method has a #Validated annotation on the class and a #Valid annotation on the method parameter which is the DTO.
With these settings, when I make a request in a test to the controller endpoint using the MockMvc instance mvc above, validation does not occur on the service method.
So is this even supposed to work?
I have the following test class:
#SpringBootTest
public class ChoreControllerTest
{
#Autowired
private ChoreController controller;
#Test
public void throwOnMissingChore()
{
assertThrows(ChoreNotFoundException.class, () -> this.controller.getChore(0L));
}
}
It takes about 5 seconds for Spring Boot to start up so the test can run. I want to reduce this time, but if I just remove the #SpringBootTest annotaton, I just get a NPE.
Is there a way to make this controller test more lightweight, or am I stuck with the startup time? I'm especially worried about what will happen to my test times if I ever want to test more than one controller....
The #SpringBootTest annotations create a Spring Context for you therefore it takes a while to start up. This annotation is mostly used for integration tests where a Spring context is required. Here are a few tips for optimizing integration tests.
If you remove the annotation the ChoreController cannot be autowired (no context available) which results in a NullpointerException.
Depending on your needs you can just use a Mocking library like Mockito to inject mocks e.g. services that your controller class needs and run the test without the #SpringBootTest.
You might want to take a look at this article for setting up those mocks properly.
I am getting ready to take my Spring Certification v5.0 and there appears to be a question: Do you use Spring in a unit test? Link to Exam Guide questions.
From Spring reference guide I know this:
The POJOs that make up your application should be testable in JUnit or
TestNG tests, with objects simply instantiated using the new operator,
without Spring or any other container.
From my study as well I can tell that we only are using Spring when testing controllers (like below), repositories or when creating integration tests and maybe some other cases. We would need the TestContext in these cases which is part of org.springframework.* package.
#RunWith(SpringRunner.class)
#WebMvcTest(HelloWorldController.class)
So, is the correct answer of this question: No we do not use Spring? or that, Yes we do need it. Because we obviously use it in some cases.
The first paragraph you mentioned is the answer to your question, you don't need Spring to write unit tests for the classes you wrote, even when they're Spring beans.
The other cases you mentioned aren't really unit tests. When testing a repository using SpringRunner and a mock database, you're no longer writing a unit test, but an integration test. The same applies to writing tests for your controller using MockMvc. In both cases you're testing the integration between the Spring framework (Spring MVC or Spring Data) with your code (and a database).
However, you can write unit tests for your controller, but in that case, you would rather do something like this:
#Controller
public class HelloWorldController {
#RequestMapping("/hello")
public ModelAndView getHello() {
return new ModelAndView("hello", "title", "hello world");
}
}
public class HelloWorldControllerTest {
private HelloWorldController controller;
#Before
public void setUp() {
controller = new HelloWorldController();
}
#Test
public void getHelloShouldUseHelloView() {
assertThat(controller.getHello().getViewName()).isEqualTo("hello");
}
#Test
public void getHelloShouldAddATitleModel() {
assertThat(controller.getHello().getModel()).containsEntry("title", "Hello world");
}
}
Spring is a framework. A framework calls you, so by definition, using spring with your controller is an integration point and does not belong in a unit test.
I have successfully been testing my controllers' behaviour without involving spring whatsoever.
I have also tested the controller's annotation successfully by asking spring to mock the controller and invoking the mockMvc.
I'm working on a Spring-Boot web application. The usual way of writing integration tests is:
#Test
#Transactional
#Rollback(true)
public void myTest() {
// ...
}
This works nicely as long as only one thread does the work. #Rollback cannot work if there are multiple threads.
However, when testing a #RestController class using the Spring REST templates, there are always multiple threads (by design):
the test thread which acts as the client and runs the REST template
the server thread which receives and processes the request
So you can't use #Rollback in a REST test. The question is: what do you use instead to make tests repeatable and have them play nicely in a test suite?
#DirtiesContext works, but is a bad option because restarting the Spring application context after each REST test method makes the suite really slow to execute; each test takes a couple of milliseconds to run, but restarting the context takes several seconds.
First of all, testing a controller using a Spring context is no unit test. You should consider writing a unit test for the controller by using mocks for the dependencies and creating a standalone mock MVC:
public class MyControllerTest {
#InjectMocks
private MyController tested;
// add #Mock annotated members for all dependencies used by the controller here
private MockMvc mvc;
// add your tests here using mvc.perform()
#Test
public void getHealthReturnsStatusAsJson() throws Exception {
mvc.perform(get("/health"))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status", is("OK")));
}
#Before
public void createControllerWithMocks() {
MockitoAnnotations.initMocks(this);
MockMvcBuilders.standaloneSetup(controller).build()
}
}
This even works if you use an external #ControllerAdvice for error handling etc by simply calling setControllerAdvice() on the MVC builder.
Such a test has no problems running in parallel and is much faster by no need to setup a Spring context at all.
The partial integration test you described is also useful to make sure the right wiring is used and all tested units work together as expected. But I would more go for a more general integration test including multiple/all endpoints checking if they work in general (not checking the edge cases) and mocking only services reaching out to external (like internal REST clients, replacing the database by one in memory, ...). With this setup you start with a fresh database and maybe will not even need to rollback any transaction. This is of course most comfortable using a database migration framework like Liquibase which would setup your in memory db on the fly.
Below is my implement followed by #4
private MockMvc mockMvc;
#Mock
private LoginService loginService;
#Mock
private PersonalService personalService;
#InjectMocks
private LoginController loginController;
#BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(loginController).build();
}
#Test
void simple_login() throws Exception {
Mockito.when(loginService.login(Mockito.anyString(), Mockito.anyString()))
.thenReturn(UserAccessData.builder()
.accessToken("access_token_content")
.refreshToken("refresh_token_count")
.build());
mockMvc.perform(
MockMvcRequestBuilders.post("/login/simple")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.param("accountName", "1234561")
.param("password", "e10adc3949ba59abbe56e057f20f883e")
)
.andDo(print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(jsonPath("$.tokens.accessToken", is("access_token_content")))
.andExpect(jsonPath("$.tokens.refreshToken", is("refresh_token_count")))
;
}
I am pretty new to Spring and I study using "Spring in Action" (fourth edition) by Craig Walls. The interest is not only on how to write code that is working, but also on the correct principles of using Spring.
Regarding the following piece of code from page 142, Listing 5.6:
public class HomeControllerTest {
#Test
public void testHomePage() throws Exception {
HomeController controller = new HomeController();
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(get("/")).andExpect(view().name("home"));
}
}
My questions are generated by the following thoughts:
The general understanding is that Spring uses Dependency Injection as a way to reduce the management overhead of object dependencies, increase modularity, simplify testing and code reuse. However, doesn't it imply that beans must be created and managed by the container? Since I started reading on the subject, the first detail that I memorized stated that new should never appear in a well-written piece of code that follows DI.
Could this be a solution in case we want to test a Stateful bean? I mean, if there are multiple independent tests to be run on the same instance, each of them testing the same state of the bean. Even though I found out that there is a suitable annotation for doing this (#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)).
Is there another use case that is difficult or impossible to solve otherwise (except for using new)?
A more 'to the letter' implementation would use #ContextConfiguration to specify the ApplicationContext.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = WebConfig.class)
#WebAppConfiguration
public class HomeControllerTest {
#Autowired
HomeController controller;
#Test
public void testHomePage() throws Exception {
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(get("/")).andExpect(view().name("home"));
}
}
yes, you shouldn't use new to create Spring bean instances (otherwise they're not Spring beans anymore) in production code. But the whole point of DI is to let you create and manually inject your objects with fake dependencies in unit tests. So the test code here is perfectly fine.
Yes, each unit test is free to create its own bean instance and to populate it the way it needs to. Stateful beans are extremely rare, though. They're usually stateless.
Another place where using new to create a Spring bean is precisely in #Bean-annotated methods of configuration classes. The whole point of these methods is precisely to create and initialize the Spring beans that will then be used and injected by Spring. But again, using new in unit tests is absolutely fine, and the right thing to do.