You need this when your pages get their services through @SpringBean and you want to test them with WicketTester. The test has to give the SpringComponentInjector a Spring context: one filled with mock beans for a fast unit test, or a context from Spring’s test support for an integration test. Inject Spring beans covers the setup of the application itself.
If the application creates its Spring context in init(), a test with new WicketTester(new WicketApplication()) already runs against that context and needs nothing from this page. The rest is for applications that let the servlet container or Spring Boot create the context. In a test there is no container, so new SpringComponentInjector(this) fails with “No WebApplicationContext found: no ContextLoaderListener registered?”.
Let tests replace the injector
Create the injector in a method of its own, so that a test can pass in a context:
WicketApplication.java
public class WicketApplication extends WebApplication {
@Override
protected void init() {
super.init();
getComponentInstantiationListeners().add(newSpringComponentInjector());
}
protected SpringComponentInjector newSpringComponentInjector() {
// finds the context that the container or Spring Boot created
return new SpringComponentInjector(this);
}
// getHomePage() and the rest of the application
}
Test with mock beans
ApplicationContextMock, part of wicket-spring, is a Spring context you fill with bean instances yourself. Each test gets its own beans and starts in milliseconds:
OrderPageTest.java
class OrderPageTest {
private final ApplicationContextMock context = new ApplicationContextMock();
private WicketTester tester;
@BeforeEach
void setUp() {
context.putBean("orderService", new InMemoryOrderService(List.of(
new Order(42, "Ada Lovelace"))));
tester = new WicketTester(new WicketApplication() {
@Override
protected SpringComponentInjector newSpringComponentInjector() {
return new SpringComponentInjector(this, context);
}
});
}
@AfterEach
void tearDown() {
tester.destroy();
}
@Test
void showsOrder() {
tester.startPage(OrderPage.class, new PageParameters().add("id", 42));
tester.assertLabel("customer", "Ada Lovelace");
}
}
putBean(bean) without a name uses the class name. A mock from Mockito works as well as a hand-written fake: context.putBean("orderService", mock(OrderService.class)). @SpringBean finds beans by type, or by name when the annotation has one.
Test against a Spring test context
For an integration test, let Spring’s TestContext framework build the context from your configuration and give it to the injector. Add spring-test with test scope, in the version of your Spring Framework.
Wicket 11 is built against Spring 7 and JUnit 6, Wicket 10 against Spring 6.2 and JUnit 5. The annotations and the test code on this page are the same for both.
OrderPageIT.java
@SpringJUnitConfig(TestConfig.class)
class OrderPageIT {
@Autowired
private ApplicationContext context;
private WicketTester tester;
@BeforeEach
void setUp() {
tester = new WicketTester(new WicketApplication() {
@Override
protected SpringComponentInjector newSpringComponentInjector() {
return new SpringComponentInjector(this, context);
}
});
}
@AfterEach
void tearDown() {
tester.destroy();
}
@Test
void savesOrder() {
tester.startPage(NewOrderPage.class);
FormTester form = tester.newFormTester("form");
form.setValue("customer", "Grace Hopper");
form.submit("save");
tester.assertRenderedPage(OrderSavedPage.class);
}
}
TestConfig can be your production configuration or a configuration for tests, for example with an in-memory database. Spring keeps the context between tests, while each test gets a new Wicket application. Do not hand a WebApplication bean from the context to the tester: every tester initializes its application, and one Spring bean would be initialized again by every test.
Roll back database changes
WicketTester handles each request on the thread of the test, so Spring’s test transactions cover what your pages do. Annotate the test method or class with @Transactional, with a transaction manager in the context, and Spring rolls back after each test:
@Test
@Transactional
void savesOrder() {
// ...
}
Pages that start their own transactions, through a service with @Transactional(propagation = REQUIRES_NEW) for instance, commit them as usual.