Reputation: 1500
JUnit 5 has features like the @BeforeEach
, @BeforeAll
, @AfterAll
, @AfterEach
annotations that allow you to execute code during test life-cycles events, for instance to clear state that would otherwise be shared between tests.
I'd like a way to do this automatically for certain test configurations without having to remember to put these life-cycle hooks in each test.
For example:
If I have a Spring test configuration that loads some stub data sources:
interface DataProvider {
String getData(String id);
}
And a class that depends on this:
@Component
class ClassThatDependsOnDataProvider {
private final DataProvider dataProvider;
@Autowired
ClassThatDependsOnDataProvider(DataProvider dataProvider) {
this.dataProvider = dataProvider;
}
public String process(String id) {
String data = dataProvider.get(id);
return some_processing_of(data);
}
}
Normally this might hit a back-end, but for my tests I will swap it out with an in-memory solution:
@TestComponent
class InMemoryDataProvider implements DataProvider {
private Map<String, String> data = new HashMap<>();
public void setData(String id, String data) {
data.put(id, data);
}
public String getData(String id) {
return data.get(id);
}
public void clear() {
data.clear();
}
}
In my test I might want to use this to stub out data in my back-end, like:
@SpringBootTest
@Import({InMemoryDataProvider.class})
class ClassThatDependsOnDataProviderTests {
@Autowired InMemoryDataProvider dataProvider;
@Autowired ClassThatDependsOnDataProvider classThatDependsOnDataProvider;
@Test
public void should_correctly_process_data() {
// given i have an id with a value
dataProvider.setData("stub_id", "stub_value");
// when i process my id
var result = classThatDependsOnDataProvider.process("stub_id");
// then i receive expected result
assertThat(result).isWhatIExpect();
}
// more @Tests
// DON'T WANT TO HAVE TO DO THIS IN EACH TEST CLASS
@AfterEach
public void tearDown() {
dataProvider.clear();
}
}
I want to be able to remove the tearDown()
method here, which is annotated with @AfterEach
, and move that behavior into either the test component/test fake or else a configuration class such that, with a set of such providers, it is easy to maintain a set of tests that will replace them with fake versions without having to manually manage their lifecycle for each test.
Is there some kind of Spring feature, lifecycle event, or JUnit 5 feature which will allow this?
Please excuse the simple example, obviously it is overkill for a test this simple but it's just a reference for discussion. Assume @MockBean
or using mocking libs is not an option.
Upvotes: 0
Views: 2514
Reputation: 448
You can use JUnit5 extensions: https://junit.org/junit5/docs/current/user-guide/#extensions.
For example, your extensions would be something like the following:
public class InMemoryDataProviderExtension implements AfterEachCallback {
/**
* {@inheritDoc}
*/
@Override
public void afterEach(final ExtensionContext context) throws Exception {
final ApplicationContext applicationContext = SpringExtension.getApplicationContext(context);
final InMemoryDataProvider inMemoryDataProvider = applicationContext.getBean(InMemoryDataProvider.class);
inMemoryDataProvider.clear();
}
}
Note that:
SpringExtension.getApplicationContext(context);
gets the spring application context, so you can retrieve the beans defined in the contextIn order to activate the extension in your test:
@SpringBootTest
@ExtendWith(InMemoryDataProviderExtension.class)
public class ClassThatDependsOnDataProviderTests { ... }
Upvotes: 3