Love Spring Testing Even More With Mocking and Unit Test Assistant
By Brian McGlauflin
December 12, 2017
7 min read
Jump to Section
The Spring Framework (along with Spring Boot) provides a helpful testing framework for writing JUnit tests for your Spring Controllers.
In this post I’ll address one of the biggest challenges of testing any complex application: dependency management.
Why Do I Need Mocking?
Let’s be honest. Complex applications are not built from scratch – they use libraries, APIs, and core projects or services that are built and maintained by someone else. As Spring developers, we leverage existing functionality as much as possible so we can spend our time and effort on what we care about: the business logic of our application. We leave the details to the libraries, so our applications have lots of dependencies, shown in orange below:
Fig 1. A Spring service with multiple dependencies
So how do I focus unit tests on my application (controller and service) if most of its functionality depends on the behavior of these dependencies? Am I not, in the end, always performing integration tests instead of unit tests? What if I need better control over the behavior of those dependencies, or the dependencies aren’t available during unit testing?
Improve Unit Testing for Java With Automation: Best Practices for Java Developers
What I need is a way to isolate my application from those dependencies, so I can focus my unit tests on my application code. In some cases, we could create specialized “testing” versions of these dependencies. However, using a standardized library like Mockito provides benefits over this approach for multiple reasons:
- You don’t have to write and maintain the special “test” code yourself
- Mocking libraries can track invocations against mocks, providing an extra layer of validation
- Standard libraries like PowerMock provide additional functionality, like mocking static methods, private methods, or constructors
- Knowledge of a mocking library like Mockito can be re-used across projects, whereas knowledge of custom testing code can’t be reused
Fig 2. A mocked service replaces multiple dependencies
Dependencies in Spring
In general, Spring applications split functionality into Beans. A Controller might depend on a Service Bean, and the Service Bean might depend on an EntityManager, JDBC connection, or another Bean. Most of the time, the dependencies from which the code under test needs to be isolated are beans. In an integration test, it makes sense that all layers should be real – but for unit tests, we need to decide which dependencies should be real and which should be mocks.
Spring allows developers to define and configure beans using either XML, Java, or a combination of both to provide a mixture of mocked and real beans in your configuration. Since mock objects need to be defined in Java, a Configuration class should be used to define and configure the mocked beans.
Mocking Dependencies
When UTA generates a Spring test, all dependencies for your controller are set up as mocks so that each test gains control over the dependency. When the test is run, UTA detects method calls made on a mock object for methods that do not yet have method mocking configured, and recommends that those methods should be mocked. We can then use a quick-fix to automatically mock each method.
Here is an example controller which depends on a PersonService:
@Controller
@RequestMapping("/people")
public class PeopleController {
@Autowired
protected PersonService personService;
@GetMapping
public ModelAndView people(Model model){
for (Person person : personService.getAllPeople()) {
model.addAttribute(person.getName(), person.getAge());
}
return new ModelAndView("people.jsp", model.asMap());
}
}
And an example test, generated by Parasoft Jtest’s Unit Test Assistant:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PeopleControllerTest {
@Autowired
PersonService personService;
// Other fields and setup
@Configuration
static class Config {
// Other beans
@Bean
public PersonService getPersonService() {
return mock(PersonService.class);
}
}
@Test
public void testPeople() throws Exception {
// When
ResultActions actions = mockMvc.perform(get("/people"));
}
}
Here, the test uses an inner class annotated with @Configuration, which provides bean dependencies for the Controller under test using Java configuration. This allows us to mock the PersonService in the bean method. No methods are mocked yet, so when I run the test, I see the following recommendation:
This means that the getAllPeople() method was called on my mocked PersonService, but the test does not yet configure mocking for this method. When I choose the “Mock it” quickfix option, the test is updated:
@Test
public void testPeople() throws Exception {
Collection<Person> getAllPeopleResult = new ArrayList<Person>();
doReturn(getAllPeopleResult).when(personService).getAllPeople();
// When
ResultActions actions = mockMvc.perform(get("/people"));
When I run the test again, it passes. I should still populate the Collection that gets returned by getAllPeople(), but the challenge of setting up my mocked dependencies is solved.
Note that I could move the generated method mocking from the test method into the Configuration class’s bean method. If I do this, it means that each test in the class will mock the same method in the same way. Leaving the method mocking in the test method means that the method can be mocked differently between different tests.
Spring Boot
Spring Boot makes bean mocking even easier. Instead of using an @Autowired field for the bean in the test and a Configuration class that defines it, you can simply use a field for the bean and annotate it with @MockBean. Spring Boot will create a mock for the bean using the mocking framework it finds on the classpath, and inject it the same way any other bean in the container can be injected.
Accelerate Unit Testing of Spring Applications with Parasoft Jtest and Its Unit Test Assistant
When generating Spring Boot tests with the Unit Test Assistant, the @MockBean functionality is used instead of the Configuration class.
@SpringBootTest
@AutoConfigureMockMvc
public class PeopleControllerTest {
// Other fields and setup – no Configuration class needed!
@MockBean
PersonService personService;
@Test
public void testPeople() throws Exception {
...
}
}
XML vs Java Configuration
In the first example above, the Configuration class provided all the beans to the Spring container. Alternatively, you can use XML configuration for the test instead of the Configuration class; or you can combine the two. For example:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:/**/testContext.xml" })
public class PeopleControllerTest {
@Autowired
PersonService personService;
// Other fields and setup
@Configuration
static class Config {
@Bean
@Primary
public PersonService getPersonService() {
return mock(PersonService.class);
}
}
// Tests
}
Here, the class references an XML configuration file in the @ContextConfiguration annotation (not shown here) to provide most beans, which could be real beans or test-specific beans. We also provide an @Configuration class, where PersonService is mocked. The @Primary annotation indicates that even if a PersonService bean is found in the XML configuration, this test will use the mocked bean from the @Configuration class instead. This type of configuration can make testing code smaller and easier to manage.
You can configure UTA to generate tests using any specific @ContextConfiguration attributes you need.
Mocking Static Methods
Sometimes, dependencies are accessed statically. For example, an application might access a 3rd-party service through a static method call:
public class ExternalPersonService {
public static Person getPerson(int id) {
RestTemplate restTemplate = new RestTemplate();
try
{
return restTemplate.getForObject("http://domain.com/people/" + id, Person.class);
} catch (RestClientException e) {
return null;
}
}
}
In our Controller:
@GetMapping
public ResponseEntity<Person> getPerson(@PathVariable("id") int id, Model
model)
{
Person person = ExternalPersonService.getPerson(id);
if (person != null) {
return new ResponseEntity<Person>(person, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
In this example, our handler method uses a static method call to get a Person object from a 3rd-party service. When we build a JUnit test for this handler method, a real HTTP call would be made to the service each time the test is run.
Instead, let’s mock the static ExternalPersonService.getPerson() method. This prevents the HTTP call, and allows us to provide a Person object response that suits our testing needs. The Unit Test Assistant can make it easier to mock static methods with PowerMockito.
UTA generates a test for the handler method above which looks like this:
@Test
public void testGetPerson() throws Throwable {
// When
long id = 1L;
ResultActions actions = mockMvc.perform(get("/people/" + id));
// Then
actions.andExpect(status().isOk());
}
When we run the test, we will see the HTTP call being made in the UTA Flow Tree. Let’s find the call to ExternalPersonService.getPerson() and mock it instead:
The test is updated to mock the static method for this test using PowerMock:
@Test
public void testGetPerson() throws Throwable {
spy(ExternalPersonService.class);
Person getPersonResult = null; // UTA: default value
doReturn(getPersonResult).when(ExternalPersonService.class, "getPerson", anyInt());
// When
int id = 0;
ResultActions actions = mockMvc.perform(get("/people/" + id));
// Then
actions.andExpect(status().isOk());
}
Using UTA, we can now select the getPersonResult variable and instantiate it, so that the mocked method call doesn’t return null:
String name = ""; // UTA: default value
int age = 0; // UTA: default value
Person getPersonResult = new Person(name, age);
When we run the test again, getPersonResult is returned from the mockedExternalPersonService.getPerson() method, and the test passes.
Note: From the flow tree, you can also choose “Add Mockable Method pattern” for static method calls. This configures Unit Test Assistant to always mock those static method calls when generating new tests.
Conclusion
Complex applications often have functional dependencies that complicate and limit a developer’s ability to unit test their code. Using a mocking framework like Mockito can help developers isolate the code under test from these dependencies, enabling them to write better unit tests more quickly. Parasoft’s unit testing tool makes dependency management easy by configuring new tests to use mocks, and by finding missing method mocks at runtime and helping developers generate mocks for them.