We love testing here at Pivotal Labs. Every pair on every project at some point asks the question, “So how are we going to test this?” If our testing strategy includes access to a data layer then it is important to know the state of the data before our tests run. In rspec this is often achieved by wrapping each test around a database transaction. If that isn’t possible (maybe the test code doesn’t have access to the DB connection) then a database cleaning strategy is often used. This strategy often truncates database tables between tests.
Here are two well-used integration testing techniques in the Spring world. The first uses transaction, the second a database clean.
Spring MVC Test Framework
The first is the Spring MVC Test Framework. This test framework does not require running a servlet container. This means that view from JSPs are not rendered at all. Other rendering such as Velocity will render views. A useful feature is the ability to wrap individual tests in transactions with annotations. Here is an example of a simple get request test wrapped in a transaction. These tests are comparable to controller tests in the Rails world.
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml")
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true)
@Transactional
public class AppTest {
private MockMvc mockMvc;
@SuppressWarnings("SpringJavaAutowiringInspection")
@Autowired
protected WebApplicationContext wac;
@Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac).build();
}
@Test
public void simple() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk());
}
}
HtmlUnit
HtmlUnit involves running a full application server and executing tests against the running server. This means that views are rendered and their content’s can be checked. These tests are very similar to our rspec integration tests written using cabybara. This test fills in some values to a form, submits it and checks that the page updates with the entered form data. As the tests run against an already running application server we can’t wrap the tests in a transaction. As we’re using flyway for database migrations this allows us to use a @FlywayTest annotation on our tests in order to run each test against a clean DB. Much like database cleaner in the rails testing world; the DB is cleaned and re-created after each test. The same Spring context configuration is used as for the application server that we are running tests against. Notice the @FlywayTest annotation on the test class. We had to use the spring4 branch from flyway-test-extenstions.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml")
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, FlywayTestExecutionListener.class })
@FlywayTest
public class FormTest {
@Test
public void postForm() throws Exception {
final WebClient webClient = new WebClient();
final HtmlPage page = webClient.getPage("http://localhost:3000/");
Assert.assertTrue(page.asText().contains("Users"));
final HtmlForm form = page.getFormByName("user");
final HtmlSubmitInput button = form.getInputByValue("Add User");
final HtmlTextInput firstName = form.getInputByName("firstName");
firstName.setValueAttribute("Jim");
final HtmlTextInput lastName = form.getInputByName("lastName");
lastName.setValueAttribute("Smith");
final HtmlTextInput email = form.getInputByName("email");
email.setValueAttribute("[email protected]");
final HtmlPage page2 = button.click();
Assert.assertTrue(page2.asText().contains("Smith, [email protected]"));
}
}
This tactic of re-running migrations after every test will certainly slow down test execution as more migrations are added. Compressing the migrations down periodically will help with this.