Want to see Parasoft in action? Sign up for our Monthly Demos! See Demos & Events >>
Want to see Parasoft in action? Sign up for our Monthly Demos! See Demos & Events >>
The 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.
Previously, our VP of Products Mark Lambert discussed how developers don’t like unit testing despite its proven track record of improvement, and detailed how Parasoft Jtest’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 unit test examples and how automated and guided testing can be leveraged in this important application framework.
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:
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.
The Parasoft Jtest Unit Test Assistant 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 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:
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).
Parasoft Jtest’s Unit Test Assistant helps developers write Spring tests in multiple ways:
Generating Spring tests in Parasoft Jtest 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. The Jtest Unit Test Assistant even adds some example assertions that you can uncomment and configure.
Parasoft Jtest supports test generation using XML or class configuration by setting the “ContextConfiguration attributes for Spring tests” option in preferences.
Managing dependencies in unit testing is critical, since much of the complexity and work comes from isolating a unit under test. The Jtest Unit Test Assistant 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, read on to my next post.)
Since Spring Boot provides simplified configuration for Beans, as well as additional annotations for tests, the Unit Test Assistant 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.
You can run generated tests using any normal JUnit runner. Parasoft Jtest provides toolbar actions that 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 the Unit Test Assistant and reported in your IDE:
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.
The Jtest Unit Test Assistant 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 (see Unit Test Assistant abbreviated as 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));
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.
class=”p1″>The Unit Test Assistant looks for various types of handler method parameters and automatically prepares the test for them in the following ways:
Running a test which does not cause the handler method to be invoked produces a recommendation like the following:
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 the Jtest Unit Test Assistant 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, the Unit Test Assistant 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.
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 Parasoft Jtest Unit Test Assistant provides not just unit test automation but guided test creation and dependency management, to accelerate test creation and reduce maintenance.
To learn more, read how the Unit Test Assistant can help with dependency management with mocking frameworks.
Brian McGlauflin is a software engineer at Parasoft with experience in full stack development using Spring and Android, API testing, and service virtualization. He is currently focused on automated software testing for Java applications with Parasoft Jtest.