The Spring framework (along with Spring Boot) is one of the most popular Java enterprise software frameworks. Its use in mission-critical applications means its quality and safety have come under scrutiny.
Previously, we discussed how developers don’t like unit testing despite its proven track record of improvement, and detailed how Parasoft Jtest’s unit testing assistant provides a guided and automated testing approach that makes testing not only more acceptable, but also easier and more effective. In this article, I’ll continue the theme of the Spring framework and show you how to leverage automation and guided testing in this important application framework.
Challenges of testing Spring applications
The Spring framework provides good support for integration testing, but setting up test cases correctly requires a lot of manual coding. Building and maintaining tests for Spring applications presents a unique set of challenges for developers, including the following:
- The Spring framework must be initialized and configured
- Applications often have dependencies on third-party libraries (persistent storage, external services, and so on)
- Applications typically use built-in Spring functionality for sessions, security, messaging, and so on. Setting up these features can be tricky for developers new to Spring testing
- Application dependencies (that is, beans) need to be configured properly
These challenges, combined with the time it typically takes to write comprehensive maintainable test suites, result in developers not writing enough tests, which in turn can lead to security holes, defects, regressions, and many headaches.
Parasoft Jtest Unit Test Assistant can help you by making the process of generating, improving, and maintaining JUnit tests easier and less time-consuming so developers can quickly build good tests and get back to doing what they prefer — writing code.
Spring MVC testing framework
The Spring framework includes a testing framework that makes it easier to test controllers, services, and other components. It includes the ability to configure the Spring test container, invoke Controller handling methods, and validate behavior with custom assertions.
Here’s an example of a 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"; }}Copy the code
In this example, the controller implements a simple REST service to retrieve items from the “to-do” list. It relies on a TodoService, which contains the business logic. To test the findAll method, we need a JUnit test that does the following:
- Configure the TodoService that the Controller under test and the TodoController depend on in the Spring container.
- Send a valid request to the findAll handler.
- Validate the elements of the response, including the return value (“todo/list”) and the Model attribute “todos”.
Another example of a Spring MVC Junit test might 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")); }}Copy the code
The above example is a very simple test — but there’s still a lot of “boilerplate” code to write and a lot to do. In this example, we use an internal Configuration class to configure a controller and its services for Spring, and then use the MockMvc function to send requests to the handler. We then use the MockMvc function to send a request to the handler method (using Perform) and use andExpect to verify the returned view name.
What was wrong with the above test? Nothing — but imagine a more complex controller approach that has multiple processing methods, takes more parameters, and produces more output. Writing tests can take more time, especially if good test coverage is important. In addition, most real-world tests require more configuration (XML or class configuration, session and environment, security, and so on).
Generate Spring tests using Parasoft Jtest
Parasoft Jtest’s unit test assistant helps developers write Spring tests in a variety of ways:
- Automatically and quickly generate template code for Spring MVC tests
- Automatically generate parameterized tests to improve test coverage
- Simulate dependencies to isolate helper methods and simplify testing
- Collect coverage data and analyze the test process at run time
- Provide suggestions for improving tests and quick fixes
Automatic test generation
Generating Spring tests in Parasoft Jtest is straightforward — just select a Spring handler for your controller in your IDE and select a test creation action:
Selecting Regular Spring or Parameterized Spring will automatically generate templated Spring MVC tests for you, including the Configuration class (and all the beans your controller depends on). The mockMvc. Perform call is also added and preconfigured to call the handler method that creates the test. The Jtest unit Test Assistant even adds some example assertions that you can uncomment and configure.
Parasoft Jtest supports generating tests using XML or class configuration by setting the “ContextConfiguration Attributes for Spring Tests “option in preferences.
Analog dependence
Managing dependencies in unit tests is critical because most of the complexity and effort comes from isolating the units under test. The Jtest unit Test assistant uses Mockito or PowerMockito by default to simulate dependencies (you can disable this in preferences if you don’t want to). Mock dependencies allow tests to control these dependencies, isolating handler methods from the rest of the application to focus testing efforts on handlers. In our example handler, the findAll method is called on TodoService — if we use a real TodoService, we’re effectively testing TodoController and TodoService. This may be what we want in integration testing, but not in unit testing. Simulating the response of todoService.findall in our tests allows us to focus our testing efforts on processing methods.
(If you want to learn more about emulating dependencies in Spring tests, read my next article.)
Spring Boot
Because Spring Boot provides simplified configuration for beans, as well as additional test annotations, slightly different tests will be generated when the unit test assistant detects Spring Boot in your project. For example, MockMvc is wired automatically, and dependencies are simulated using @MockBean and annotated with @SpringBooTtest.
Run tests and analyze results
You can run the generated tests using any normal JUnit runner. Parasoft Jtest provides toolbar operations for running JUnit and analysis tests.
After the test runs, the test execution process is displayed, and the Unit Test assistant suggests improvements to the test and reports them in the IDE:
Provide handler method input
Handler methods are typically configured to accept paths, queries, or other parameters as method parameters. To test the MVC handler method, you can use MockMvc to build the path/query and any other parameters needed to call the method.
The Jtest unit test assistant automatically configures the mockMvc. Perform call to invoke handler methods. Individual parameters are shown in tests as local variables (or parameters in parameterized tests) that need to be configured for the tests to run correctly.
For example (unit Test Assistant abbreviated UTA below) :
@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));
Copy the code
Here, you need to configure the “ID” string — if not, the path used will be “/people/”, and Spring will not match the provided path to the appropriate processing method.
Class =” P1 “> Unit Test assistant looks for various types of handler method parameters and automatically prepares tests for them by:
- HttpSession (add an example of a setAttribute() call)
- Header (add a header() call)
- Request body (add a payload variable and a Content () call)
- Authentication (adding an instance to the setup method, along with a principal() call)
Running a test that does not cause handler methods to be called produces suggestions like the following:
Validate the output of the handler method
Depending on what the handler method is providing to the caller, it may return multiple types. In most cases, the handler returns either a ModelAndView (or a similar object, such as Model or RedirectView) to service the page, or a ResponseEntity of some type (sometimes just the original object to serialize). This response can be verified by accessing the Spring MVC test framework.
For example, the Jtest unit Test assistant adds the following assertion to a processing method that returns 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("", ""));
Copy the code
Once the tests are generated, you can uncomment these assertions and fill in the values to quickly set up a useful and valuable test. If an assertion fails at run time, the unit Test assistant provides a suggestion and quick fix to automatically update the expected value or simply remove the assertion. To quickly set a correct assertion value, you can uncomment the assertion, let it fail, and then use a quick fix to set the correct expected value.
conclusion
Spring (in conjunction with Spring Boot) is the leading enterprise-class Java application framework, so an appropriate level of testing is required to ensure the quality and security of the applications built with it. Unfortunately, this level of testing is not yet available, mainly due to lack of time and the amount of manual coding and maintenance required. The Parasoft Jtest Unit Test Assistant not only provides unit test automation, but also instructional test creation and dependency management to speed up test creation and reduce maintenance.
To learn more, read how the unit Test assistant uses the mock framework to help with dependency management.