Featured Webinar: AI-Enhanced API Testing: A No-Code Approach to Testing | Watch Now

Why You Should Start Using Mocking for Spring Boot Unit Testing Right Now

Headshot of Brian McGlauflin,
June 5, 2023
7 min read

Writing JUnit tests for your Spring applications is made easier by the testing infrastructure offered by the Spring Framework and Spring Boot. Go through this expertly curated post to learn more.

The Spring Framework, along with Spring Boot, provides a helpful testing framework for writing JUnit tests for your Spring applications. In this post, I’ll address one of the biggest challenges of testing any complex application: dependency management.

What Does Mocking Mean in Spring Boot?

Mocking is a technique used in unit testing to simulate the behavior of real objects when the unit being tested has external dependencies. The simulations, or mocks, are used in place of the real objects. The goal of mocking is to isolate and concentrate on the code being tested and not on the behavior or state of external dependencies.

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:

Graphic showing the multiple dependencies of a Spring service. From controller to service, then to either a database or libraries.
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?

The Benefits of Mocking

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 Mockito 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.
A graphic showing how a mocked service can replace multiple dependencies. Controller goes to service or a mocked service. The service also connects to a database and libraries while the mocked service does not.
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.

How Do I Start Mocking Dependencies in a Spring Test?

An automated testing tool, like Parasoft Jtest’s Unit Test Assistant (UTA), can help you create meaningful unit tests that test the functionality of your Spring applications. 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 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;

    MockMvc mockMvc;
 
    // 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:

Mocking test recommendation in Parasoft Jtest

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” quick fix 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.

How Do I Mock Dependencies in 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.

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 third-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&amp;lt;Person&amp;gt; getPerson(@PathVariable("id") int id, Model model)
    {
        Person person = ExternalPersonService.getPerson(id);
        if (person != null) {
            return new ResponseEntity&amp;lt;Person&amp;gt;(person, HttpStatus.OK);
        }
        return new ResponseEntity&amp;lt;&amp;gt;(HttpStatus.NOT_FOUND);
    }

In this example, our handler method uses a static method call to get a Person object from a third-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 Mockito.

UTA generates a test for the handler method above which looks like this:

@Test
public void testGetPerson() throws Throwable {
    // When
    Int 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:

Screenshot showing Parasoft Jtest's Unit Test Assistant flow tree

The test is updated to mock the static method for this test using Mockito:

@Test
public void testGetPerson() throws Throwable {
    MockedStatic<ExternalPersonService>mocked = mockStatic(ExternalPersonService.class);
    mocks.add(mocked);
 
    Person getPersonResult = null; // UTA: default value
    mocked.when(()->ExternalPersonService.getPerson(anyInt())).thenReturn(getPersonResult);
 
    // When
    int id = 1;
    ResultActions actions = mockMvc.perform(get("/people/" + id));

    // Then
    actions.andExpect(status().isOk());

    }
 
    Set<AutoCloseable> mocks = new HashSet&amp;lt;&amp;gt;();
 
    @After
    public void closeMocks() throws Throwable {
        for (AutoCloseable mocked : mocks) {
            mocked.close();
        }
    }

The Mockito mockStatic method creates a static mock for the class, through which specific static calls can be configured. To ensure that this test doesn’t affect others in the same run, MockedStatic objects must be closed when the test ends, so all mocks are closed in the closeMocks() method which is added to the test class.

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=newPerson(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 Java developer productivity solution 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.

Improve Unit Testing for Java With Automation: Best Practices for Java Developers