I've been desperately trying to build an extension that requires information from both the JUnit5 extension model and the Spring-Boot Test framework. Specifically, I'd like to hook into the ApplicationContext creation process using a ApplicationContextInitializer and a custom annotation:
#Retention(RUNTIME)
#Target(TYPE)
#ContextConfiguration(initializers = CustomContextInitializer.class)
public #interface CustomAnnotation {
String someOption();
}
The test then looks like this:
#SpringBootTest
#CustomAnnotation(someOption = "Hello There")
public class SomeTest {
...
}
Now, how do I access the CustomAnnotation instance of the test class from within my CustomContextInitializer?
class CustomContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// How to access the Test Class here?
CustomAnnotation annotation = <Test Class>.getAnnotation(CustomAnnotation.class);
System.out.println(annotation.someOption());
}
}
Is it possible to somehow access the JUnit5 ExtensionContext during the creation of the ApplicationContext? It doesn't have to be from within a ApplicationContextInitializer. I just need a hook that is executed early enough so that I can inject some dynamically generated properties before the whole bean instantiation process actually starts.
Take a look at #DynamicPropertySource for injecting properties before the bean initialization. You can then use #RegisterExtension to register a custom extension that reads the annotation properties and makes them available through some method:
#CustomAnnotation(someOption = "Hello There")
public class SomeTest {
#RegisterExtension
static CustomExtension extension = new CustomExtension();
#DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("property.you.need",
() -> customExtension.getProperty());
}
}
public class CustomExtension implements BeforeAllCallback {
private String property;
public String getProperty() {
return property;
}
#Override
public void beforeAll(ExtensionContext context) throws Exception {
CustomAnnotation annotation = context.getRequiredTestClass()
.getAnnotation(CustomAnnotation.class);
property = annotation.someOption();
}
}
I know it doesn’t answer the question about hooking JUnit 5 with Spring initialization mechanism, but if dynamic properties is all you need, this solves exactly that.
You can implement your own TestExecutionListener and use it to access annotation you mentioned
#Retention(RUNTIME)
#Target(ElementType.TYPE)
#TestExecutionListeners(listeners = CustomTestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#interface CustomAnnotation {
String someOption();
}
static class CustomTestExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
final CustomAnnotation annotation = testContext.getTestClass().getAnnotation(CustomAnnotation.class);
System.out.println(annotation.someOption());
}
}
Related
I have a CMS (Content management System) to store property key values.
public interface CMSConfig {
#Property(propertyName = "someConfig")
String getSomeConfig();
}
And the Configuration annotation code is
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Configuration {
String configName() default "";
}
And the way to call this CMS is
#Service
public class MockitoService {
#ManagedConfiguration
private CMSConfig cmsConfig;
public String method() {
return "Hello!" + cmsConfig.getSomeConfig();
}
}
ManagedConfiguration.java
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD})
public #interface ManagedConfiguration {
}
I am looking to do the UnitTest for CMSConfig
I am not able to Mock or create the bean of CMSConfig as it's an interface. I have tried multiple approaches.
My UnitTest case file is
#ExtendWith(MockitoExtension.class)
public class MockitoServiceTest {
#Autowired
MockitoService mockitoService;
#ManagedConfiguration
private CMSConfig cmsConfig;
#BeforeAll
public static void before() {
System.setProperty("cms.configs.dir", Paths.get("src", "main", "resources").toFile().getAbsolutePath());
}
#Test
public void testCMS(){
assertEquals(cmsConfig.getDummy(),"dummyvalueUatRes");
}
}
Please help if there's any way to do this.
My mockitoService and cmsConfig are null while running the test case.
I have a test utility for with I need to have a fresh instance per test method (to prevent that state leaks between tests). So far, I was using the scope "prototype", but now I want to be able to wire the utility into another test utility, and the wired instances shall be the same per test.
This appears to be a standard problem, so I was wondering if there is a "test method" scope or something similar?
This is the structure of the test class and test utilities:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private TestDriver driver;
#Autowired
private TestStateProvider state;
// ... state
// ... methods
}
#Component
#Scope("prototype") // not right because MyTest and TestStateProvider get separate instances
public class TestDriver {
// ...
}
#Component
public class TestStateProvider {
#Autowired
private TestDriver driver;
// ...
}
I'm aware that I could use #Scope("singleton") and #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) but this refreshes more than I need – a new TestDriver instance for each test would be enough. Also, this approach is error-prone because all tests using the TestDriver would need to know that they also need the #DirtiesContext annotation. So I'm looking for a better solution.
It is actually pretty easy to implement a testMethod scope:
public class TestMethodScope implements Scope {
public static final String NAME = "testMethod";
private Map<String, Object> scopedObjects = new HashMap<>();
private Map<String, Runnable> destructionCallbacks = new HashMap<>();
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
#Override
public Object remove(String name) {
throw new UnsupportedOperationException();
}
#Override
public String getConversationId() {
return null;
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
public static class TestExecutionListener implements org.springframework.test.context.TestExecutionListener {
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
.getApplicationContext();
TestMethodScope scope = (TestMethodScope) applicationContext.getBeanFactory().getRegisteredScope(NAME);
scope.destructionCallbacks.values().forEach(callback -> callback.run());
scope.destructionCallbacks.clear();
scope.scopedObjects.clear();
}
}
#Component
public static class ScopeRegistration implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope(NAME, new TestMethodScope());
}
}
}
Just register the test execution listener, and there will be one instance per test of all #Scope("testMethod") annotated types:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestExecutionListeners(listeners = TestMethodScope.TestExecutionListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
public class MyTest {
#Autowired
// ... types annotated with #Scope("testMethod")
}
I ran into the same problem some time ago and came to this solution:
Use Mocks
I wrote some methods to create specific mockito settings to add behavior to each mock.
So create a TestConfiguration class with following methods and bean definition.
private MockSettings createResetAfterMockSettings() {
return MockReset.withSettings(MockReset.AFTER);
}
private <T> T mockClass(Class<T> classToMock) {
return mock(classToMock, createResetAfterMockSettings());
}
and your bean definition will look like:
#Bean
public TestDriver testDriver() {
return mockClass(TestDriver .class);
}
MockReset.AFTER is used to reset the mock after the test method is run.
And finally add a TestExecutionListeners to your Test class:
#TestExecutionListeners({ResetMocksTestExecutionListener.class})
I've created a custom qualifier and created a class with a method to produce the value for it
public class ValueProducer {
#Produces
#ConfigurationValue
public String getStringConfigValue(InjectionPoint ip) {
...
}
}
#ConfigurationValue is my own created qualifier. Everything works fine when using it, so no problem on this site.
But I don't know how to emulate the InjectionPoint to create proper unit tests. The interface has a lot of methods to fill when creating an implementing class. Is there a simple way to create such an object without implementing the interface on my own? I also haven't found any example how anybody is testing these kind of methods.
I would not bother with mocking and just write some kind of CDI integration tests. Here is an example using CDI-Unit (You could also use Arquillian or DeltaSpike test module for this):
#RunWith(CdiRunner.class)
#AdditionalClasses({ValueProducerTest.ValueProducer.class})
public class ValueProducerTest {
#Inject
#ConfigurationValue
private String testValue;
#Test
public void test() {
assertEquals(testValue, "Test");
}
public static class ValueProducer {
#Produces
#ConfigurationValue
public String getStringConfigValue(InjectionPoint ip) {
return "Test";
}
}
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD})
public static #interface ConfigurationValue {}
}
I have the following simple webservice that requires a LocalDate in future. The annotation is a custom one, as follows:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = DateInFutureValidator.class)
public #interface DateInFuture {
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class DateInFutureValidator implements ConstraintValidator<DateInFuture, Temporal> {
#Override
public void initialize(DateInFuture constraintAnnotation) { }
#Override
public boolean isValid(Temporal value, ConstraintValidatorContext context) {
//validation logic
}
}
Question: how can I disable that annotation during a junit integration test (that uses a date that will be in the past in a while)?
The spring servlet:
#RestController
public class MyController {
#PostMapping(path)
public Rsp post(MyDTO dto) {
}
}
public class MyDTO {
#NotNull
#DateInFuture
private LocalDate date;
}
The ITest:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringItest {
#Autowired
protected MockMvc mvc;
#Test
public void springItest() {
mvc.perform(MockMvcRequestBuilders
.post(path)
.contentType(MediaType.APPLICATION_JSON)
.content(requestAsJson)
.andExpect(status().isOk());
}
}
You can do this with a standalone setup of a MockMvc instance by providing a mock validator which will prevent it from performing any validation.
Validator mockValidator = mock(Validator.class);
MockMvc mockMvc = MockMvcBuilder.standaloneSetup(myController).setValidator(mockValidator);
If you want to do a web application set up of your MockMvc, which is the case with the auto configured MockMvc instance you are currently using then I think you should be able to provide a mock Validator to the whole context by just declairing a #MockBean Validator:
#MockBean(name = "mvcValidator")
private Validator mockValidator;
As #membersound has already mentioned that the DateInFutureValidator is not part of the project, so he needn't test the DateInFutureValidator at all. so we can just disable it by use the trick of ClassLoader how to load classes.
When there is a class with full qualified name as library class in the test source directory, the ClassLoader will load it and ignore the library class.
package ${package_name_same_as_library_class};
public class DateInFutureValidator implements ConstraintValidator<DateInFuture, Temporal> {
#Override
public void initialize(DateInFuture constraintAnnotation) { }
#Override
public boolean isValid(Temporal value, ConstraintValidatorContext context) {
return true;
}
}
The downside of this approach is it will disable DateInFutureValidator globally. but it doesn't matter in this case since it run in integration test only.
I have a interface here
interface Idemo{
public int getDemo(int i);
}
And it's one implementation
class DemoImpl implements Idemo{
#Override
public int getDemo(int i){
return i+10;
}
}
And there is a class which has a dependency on Idemo
class Sample{
#Inject
Idemo demo;
public int getSample(int i){
return demo.getDemo(i);
}
}
Now say I want to test Sample class
public class SampleTest extends JerseyTest {
#Inject
Sample s;
#Override
protected Application configure() {
AbstractBinder binder = new AbstractBinder() {
#Override
protected void configure() {
bind(Demo.class).to(Idemo.class);
bind(Sample.class).to(Sample.class); //**doesn't work**
}
};
ResourceConfig config = new ResourceConfig(Sample.class);
config.register(binder);
return config;
}
#Test
public void test_getSample() {
assertEquals(15, s.getSample(5)); //null pointer exception
}
}
Here the Sample instance is not getting created and s remains null.I suppose this is because by the time the execution reaches line where binding is specified this test class has already been created.But I am not sure.With Spring Autowired instead of jersey CDI the same works
Had Sample been a resource/controller class the test framework would create an instance of it with no need to inject it but is it possible to test any other non-web class using Jersey DI ?
The reason it works with Spring is that the test class is managed by the Spring container by using #RunWith(SpringJUnit4ClassRunner.class). The runner will inject all managed objects into the test object. JerseyTest is not managed this way.
If you want, you can create your own runner, but you need to understand a bit how HK2 (Jersey's DI framework) works. Take a look at the documentation. Everything revolves around the ServiceLocator. In a standalone, you might see something like this to bootstrap the DI container
ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
ServiceLocator locator = factory.create(null);
ServiceLocatorUtilities.bind(locator, new MyBinder());
Then to get the service, do
Service service = locator.getService(Service.class);
In the case of the test class, we don't need to gain any access to the service object, we can simply inject the test object, using the ServiceLocator:
locator.inject(test);
Above, test is the test class instance that gets passed to us in our custom runner. Here is the example implementation of a custom runner
import java.lang.annotation.*;
import org.glassfish.hk2.api.*;
import org.glassfish.hk2.utilities.*;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.*;
public class Hk2ClassRunner extends BlockJUnit4ClassRunner {
private final ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
private Class<? extends Binder>[] binderClasses;
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public static #interface Binders {
public Class<? extends Binder>[] value();
}
public Hk2ClassRunner(Class<?> cls) throws InitializationError {
super(cls);
Binders bindersAnno = cls.getClass().getAnnotation(Binders.class);
if (bindersAnno == null) {
binderClasses = new Class[0];
}
}
#Override
public Statement methodInvoker(FrameworkMethod method, final Object test) {
final Statement statement = super.methodInvoker(method, test);
return new Statement() {
#Override
public void evaluate() throws Throwable {
ServiceLocator locator = factory.create(null);
for (Class<? extends Binder> c : binderClasses) {
try {
ServiceLocatorUtilities.bind(locator, c.newInstance());
} catch (InstantiationException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
locator.inject(test);
statement.evaluate();
locator.shutdown();
}
};
}
}
In the runner, the methodInvoker is called for every test method, so we are creating a fresh new set of objects for each test method called.
Here is a complete test case
#Binders({ServiceBinder.class})
#RunWith(Hk2ClassRunner.class)
public class InjectTest {
public static class Service {
#Inject
private Demo demo;
public void doSomething() {
System.out.println("Inside Service.doSomething()");
demo.doSomething();
}
}
public static class Demo {
public void doSomething() {
System.out.println("Inside Demo.doSomething()");
}
}
public static class ServiceBinder extends AbstractBinder {
#Override
protected void configure() {
bind(Demo.class).to(Demo.class);
bind(Service.class).to(Service.class);
}
}
#Inject
private Service service;
#Test
public void testInjections() {
Assert.assertNotNull(service);
service.doSomething();
}
}
I was facing the same situation but in the context of running some integrations test that needs to have some of the singletons that my application have already defined.
The trick that I found is the following. You just need to create a normal test class or a standalone that use the DropwizardAppRule
In my case, I use JUnit as I was writing some integration test.
public class MyIntegrationTest{
//CONFIG_PATH is just a string that reference to your yaml.file
#ClassRule
public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
new DropwizardAppRule<>(XXXApplication.class, CONFIG_PATH);
}
The #ClassRule will start your application like is said here . That
means you will have access to everything and every object your application needs to start. In my case, I need to get access to a singleton for my service I do that using the #Inject annotation and the #Named
public class MyIntegrationTest {
#ClassRule
public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
new DropwizardAppRule<>(XXXAplication.class, CONFIG_PATH);
#Inject
#Named("myService")
private ServiceImpl myService;
}
Running this will set to null the service as #Inject is not working because we don't have at this point anything that put the beans into the references. There is where this method comes handy.
#Before
public void setup() {
ServiceLocator serviceLocator =((ServletContainer)APP_RULE.getEnvironment().getJerseyServletContainer()).getApplicationHandler().getServiceLocator();
//This line will take the beans from the locator and inject them in their
//reference, so each #Inject reference will be populated.
serviceLocator.inject(this);
}
That will avoid creating other binders and configurations outside of the existing on your application.
Reference to the ServiceLocator that DropwizardAppRule creates can be found here