Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Love Spring Testing Even More With Mocking and Unit Test Assistant

DZone's Guide to

Love Spring Testing Even More With Mocking and Unit Test Assistant

As we continue using Parasoft's Unit Test Assistant, let's see how it can help address the complexities of dependency management.

· Java Zone ·
Free Resource

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

Spring Framework (along with Spring Boot) provides a helpful testing framework for writing JUnit tests for your Spring Controllers. In my previous post, we talked about how to build and improve these tests efficiently with Parasoft Jtest's Unit Test Assistant. In this post, I'll continue by addressing one of the biggest challenges of testing any complex application: dependency management.

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?

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" 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.

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 3 rd-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 3 rd-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 = 1 L;
    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 mocked ExternalPersonService.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. The Unit Test Assistant 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.

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

Topics:
java ,unit test assistant ,mocking ,dependency management ,tutorial ,spring testing

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}