Start to Love Spring Testing With the Unit Test Assistant for Java
In this post, we take a look at a tool that can help make your unit testing life a bit easier. Read on to find out to see if the Unit Test Assistant can help you out!
Join the DZone community and get the full member experience.
Join For FreeThe Spring framework (along with Spring Boot) is one of the most popular Java enterprise software frameworks. Its use in mission-critical applications means it has come under scrutiny for quality and security. In a previous post, we discussed how developers don’t like unit testing despite its proven track record of improvement, and detailed how Parasoft’s Unit Test Assistant can provide a guided and automated approach to testing to make testing not only more palatable, but also easier and more efficient. In this post, I'll continue the same theme with the Spring framework, showing you how automated and guided testing can be leveraged in this important application framework. From here on out, I'll refer to the Unit Test Assistant by its acronymn, UTA.
The Challenges of Testing Spring Applications
The Spring framework comes with nice support for integration testing, but a lot of manual coding is required to set up test cases properly. Building and maintaining tests for Spring applications presents developers with a unique set of challenges, including the following:
- The Spring framework must be initialized and configured
- The application usually has third-party library dependencies (persistent storage, external services, etc.)
- Applications often use built-in Spring features for sessions, security, messaging, etc. These can be tricky to set up for developers who are new to Spring testing
- Application dependencies (i.e. beans) need to be configured appropriately
These challenges, combined with the fact that writing comprehensive and maintainable test suites takes a lot of time in general, result in developers not writing enough tests. In turn, this leads to security vulnerabilities, defects, regressions, and lots of headaches.
UTA helps by making the process of generating, improving, and maintaining JUnit tests far easier and less time-consuming, so that developers build good tests quickly and get back to what they presumably love – writing code.
The Spring MVC Test Framework
The Spring Framework includes a testing framework that makes testing Controllers, Services, and other components much easier. It includes functionality for configuring the Spring test container, invoking Controller handler methods, and validating behavior with custom assertions.
An example Spring MVC Controller:
@Controller
public class TodoController {
@Autowired
private TodoService service;
@GetMapping("/")
public String findAll(Model model) {
List<Todo> todos = service.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
}
This example controller implements a simple REST service to get items from a “to-do” list. It depends on a TodoService, which contains the business logic.
To test the findAll method, we need a Junit test which does the following:
- Configure the Spring container with the Controller under test, and a TodoService, which TodoController depends on.
- Send a valid request to the findAll handler method.
- Validate elements of the response, including the return value (“todo/list”) and Model attribute “todos”.
An example Spring MVC Junit test may look like this:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TodoControllerTest {
@Autowired
TodoController controller;
@Autowired
TodoService todoService;
MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Configuration
static class Config {
@Bean
public TodoController getTodoController() {
return new TodoController();
}
@Bean
public TodoService getTodoService() {
return new TodoService();
}
}
@Test
public void testFindAll() throws Exception {
mockMvc.perform(get("/")).andExpect(view().name("todo/list"));
}
}
The above example is a very simple test – but there’s still a lot of “boiler-plate” code to write, and a lot going on. In this example, we have configured Spring with a controller and its services, using an inner Configuration class. We then use the MockMvc functions to send a request to the handler method (using perform), and validate the returned view name using andExpect.
What’s wrong with the above test? Nothing really – but imagine a more complex controller method, with multiple handler methods that accept more arguments and produce more outputs. Writing the tests would take a lot more time, especially if good testing coverage is important. In addition, most real tests require significantly more configuration (XML or class configurations, sessions and environments, security, etc).
Generating Spring Tests With UTA
The Unit Test Assistant helps developers write Spring tests in multiple ways:
- Auto-generating the boiler-plate code for Spring MVC tests quickly
- Auto-generating parameterized tests for increased test coverage
- Mocking dependencies to isolate the helper method and simplify the test
- Collecting coverage data and analyzing test flow at runtime
- Providing recommendations with quick-fixes to improve tests
Auto-Generating Tests
Generating Spring tests in UTA is straightforward – simply select a Spring handler method in your IDE for your controller and choose a test-creation action:
Choosing either Regular Spring or Parameterized Spring auto-generates the boiler-plate Spring MVC test for you, including the Configuration classes (and all Beans that your controller depends on). The mockMvc.perform call is added too, and is preconfigured to invoke the handler method for which the test was created. UTA even adds some example assertions that you can uncomment and configure.
UTA supports test generation using XML or class configuration by setting the “ContextConfiguration attributes for Spring tests” option in preferences.
Mocking Dependencies
Managing dependencies in unit testing is critical, since much of the complexity and work comes from isolating a unit under test. UTA uses Mockito or PowerMockito to mock dependencies by default (you can disable this in preferences if you don’t want this). Mocking dependencies allows the test to control those dependencies, isolating the handler method from the rest of the application to focus testing efforts on the handler. In our example handler, the findAll method was invoked on TodoService– if we use a real TodoService, we are effectively testing both the TodoController and the TodoService. This may be what we want for an integration test, but not for a unit test. Mocking the response of TodoService.findAll in the test allows us to focus our testing efforts on the handler method.
(If you want more info about mocking dependencies in Spring tests, I'll adress it in a future post.)
Spring Boot
Since Spring Boot provides simplified configuration for Beans, as well as additional annotations for tests, UTA generates slightly different tests when it detects Spring Boot in your project. For example, MockMvc is autowired, dependencies are mocked with @MockBean, and the @SpringBootTest annotation is used.
Running Tests and Analyzing Results
You can run generated tests using any normal JUnit runner. UTA provides toolbar actions which run the Junit and analyze the test.
After tests are run, the test execution flow is shown, and recommendations for improving the test are made by UTA and reported in your IDE:
Providing Handler Method Inputs
Handler methods are often configured to accept path, query, or other parameters as arguments to the method. To test the MVC handler method, you can use MockMvc to build the path/query and any other parameters needed to invoke the method.
UTA auto-configures the mockMvc.perform call to invoke the handler method. Individual parameters show up in the test as local variables (or parameters in a parameterized test) which need to be configured for the test to run properly.
For example:
@Test
public void testGetPerson() throws Throwable {
// When
String id = ""; // UTA: Configure an appropriate parameter value since the tested method depends on it
ResultActions actions = mockMvc.perform(get("/people/" + id));
Here, the “id” String needs to be configured – if not, then the path used would be “/people/”, and Spring will not match the provided path to the appropriate handler method.
UTA looks for various types of handler method parameters and automatically prepares the test for them in the following ways:
- HttpSession (adds an example setAttribute() call)
- Headers (adds a header() call),
- Request body (adds a payload variable and content() call)
- Authentication (adds an example instantiation to the setup method, and a principal() call)
Running a test which does not cause the handler method to be invoked produces a recommendation like the following:
Validating Handler Method Outputs
Depending on what the handler method is supposed to provide to callers, it may return a variety of types. Most of the time, handler methods return either a ModelAndView (or similar objects like Model or RedirectView) to serve a page, or a ResponseEntity of some kind (sometimes just the raw object to be serialized). This response is accessible to the Spring MVC Test framework for validation.
For example, the following assertions were added by UTA for a handler method which returns a ModelAndView:
// When
String id = "1";
ResultActions actions = mockMvc.perform(get("/people/" + id));
// Then
// actions.andExpect(status().isOk());
// actions.andExpect(header().string("", ""));
// actions.andExpect(view().name(""));
// actions.andExpect(model().attribute("", ""));
Once the test is generated, you can uncomment these assertions and populate values to quickly build a useful and valuable test. If an assertion fails at runtime, UTA provides a recommendation and quick-fix to automatically update the expected value or simply remove the assertion. To quickly set up the assertion with a proper value, you can uncomment an assertion, let it fail, and then use a quick-fix to set the correct expected value.
To Conclude...
Spring (combined with Spring Boot) is the leading enterprise Java application framework and as such needs an appropriate level of testing to ensure the quality and security of applications built with it. But unfortunately, this level of testing is not being achieved currently, mostly due to a lack of time and the amount of manual coding and maintenance required. The Java Unit Test Assistant provides not just unit test automation but guided test creation and dependency management, to accelerate test creation and reduce maintenance.
Published at DZone with permission of Brian McGlauflin, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments