Have a custom TestContextBootstrapper in a meta annotation - java

I've a meta-annotation that annotates my test classes:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#SpringBootTest
#BootstrapWith(MyOwnTestContextBootstrapper.class)
public #interface IntegrationTest {
.....
And my test class looks like:
#IntegrationTest
class CreateSubscriptionServiceTest {
#Autowired
.......
Then I got this exception:
found multiple declarations of #BootstrapWith
details:
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class [com.my.project.CreateSubscriptionServiceTest]: [#org.springframework.test.context.BootstrapWith(value=com.my.project.MyOwnTestContextBootstrapper), #org.springframework.test.context.BootstrapWith(value=org.springframework.boot.test.context.SpringBootTestContextBootstrapper)]
at org.springframework.test.context.BootstrapUtils.resolveExplicitTestContextBootstrapper(BootstrapUtils.java:176)
at org.springframework.test.context.BootstrapUtils.resolveTestContextBootstrapper(BootstrapUtils.java:130)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:122)
at org.junit.jupiter.engine.execution.ExtensionValuesStore.lambda$getOrComputeIfAbsent$4(ExtensionValuesStore.java:86)
at org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.computeValue(ExtensionValuesStore.java:223)
at org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.get(ExtensionValuesStore.java:211)
at org.junit.jupiter.engine.execution.ExtensionValuesStore$StoredValue.evaluate(ExtensionValuesStore.java:191)
....

I figured the answer.
Replace #SpringBooTest with #ExtendWith(SpringExtension.class) so the meta-annotation looks like:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#ExtendWith(SpringExtension.class)
#BootstrapWith(MyOwnTestContextBootstrapper.class)
public #interface IntegrationTest {
.....

Related

How to externalize #SpringBootApplication configuration?

I want to externalize the #SpringBootApplication(exclude...) option, to have a reusable class or annotation that I could throw in to exclude any database/hibernate initialization.
So, instead of writing:
#SpringBootApplication(
exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
public class MainApp {
}
I would like to create a annotation that I could apply to my #SpringBootApplication main class:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
#Configuration
public #interface ExcludeDataSources {
}
And then enable this feature by annotation:
#SpringBootApplication
#ExcludeDataSources
public class MainApp {
}
Problem: the annotation approach does not work, and spring still tries to load a database. Why?
My final goal is to have multiple startup classes, where only one loads the database.
I could manage it by adding an additional #EnableAutoConfiguration that is only executed on a certain condition.
This way I can dynamically exclude the database config, while keeping a clean basis main #SpringBootConfiguration class.
public class DataSourceConfig {
#Configuration
#Conditional(MyCondition.class)
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
static class ExcludeDataSource {
}
}

How can I use default configuration and default test listeners for all my Spring tests?

I have several dozen tests that all use the same configuration and listeners for all tests. This means the follow lines are repeated:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = WebDriverConfig.class)
#TestExecutionListeners(listeners = {ScreenshotTaker.class, DependencyInjectionTestExecutionListener.class})
I've created the following:
public class WebDriverRunner extends SpringJUnit4ClassRunner {
public WebDriverRunner(Class<?> clazz) throws InitializationError {
super(clazz);
}
#Override
protected TestContextManager createTestContextManager(Class<?> clazz) {
return super.createTestContextManager(ConfigShim.class);
}
#ContextConfiguration(classes = WebDriverConfig.class)
#TestExecutionListeners(listeners = {ScreenshotTaker.class, DependencyInjectionTestExecutionListener.class})
public static class ConfigShim {
}
}
Which means I can run tests as follows:
#RunWith(WebDriverRunner.class)
public class ShoppingCartPageIT {
But, this changes the names of the test.
If you use Spring Framework 4.1 or higher, you can create your own composed annotation for testing configuration:
Define your annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#ContextConfiguration(classes = WebDriverConfig.class)
#TestExecutionListeners({
ScreenshotTaker.class,
DependencyInjectionTestExecutionListener.class
})
public #interface WebDriverTestConfig {}
and then annotate your tests:
#RunWith(SpringJUnit4ClassRunner.class)
#WebDriverTestConfig
public class ShoppingCartPageIT { /* ... */ }

Number of annotations at JUnit test incorrect

I have created some custom annotations to use for system tests which are run via JUnit.
A test e.g. looks like this:
#TestCaseName("Change History")
public class ChangeHistory extends SystemTestBase
{
#Test
#Risk(1)
public void test()
{
...
I am now implementing a Test Runner which shall report the test name, the risk and the somewhere for documentation purposes.
public class MyRunner extends BlockJUnit4ClassRunner
{
...
#Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier)
{
...
System.out.println("Class annotations:");
Annotation[] classanno = klass.getAnnotations();
for (Annotation annotation : classanno) {
System.out.println(annotation.annotationType());
}
System.out.println("Method annotations:");
Annotation[] methanno = method.getAnnotations();
for (Annotation annotation : methanno) {
System.out.println(annotation.annotationType());
}
The output is
Class annotations:
Method annotations:
interface org.junit.Test
So getAnnotations() seems to return annotations of JUnit only and not all annotations. This is not mentioned in the documentation of JUnit:
Returns the annotations on this method
The return type is java.lang.Annotation which made me believe that I can use any annotation. I defined the annotation like follows - I just used it and when there was an error I let Eclipse generate the annotation:
public #interface Risk {
int value();
}
How do I get all annotations of the test class and test method?
You need to set the retention policy of the Risk annotation to RUNTIME. Otherwise, the annotation will be discarded after the compilation and won't be available during the execution of the code.
This should be working:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface Risk {
int value();
}

Java annotations - code simplifications

I am looking for a way to simplify the following code.
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
// My configuration classes
})
public class MyServiceTest {
#Autowired
private MyService service;
#Test
public void myTest() {
Assert.assertTrue(service != null);
}
}
I have many configuration classes and I don't want to put them into each test class. So I got the idea to create my own annotation:
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
// My configuration classes
})
public #interface IntegrationTests {
}
I try to use it in the following way:
#IntegrationTests
public class MyServiceTest {
#Autowired
private MyService service;
#Test
public void myTest() {
Assert.assertTrue(service != null);
}
}
But it doesn't work. Any idea?
You can place these annotation on a superclass:
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
// My configuration classes
})
public abstract class AbstractIntegrationTest { ... }
.
public class MyServiceTest extends AbstractIntegrationTest { ... }
This approach also allows you to declare common #Autowired dependencies in the base class and customize #ContextConfiguration classes in concrete tests.
The reason your custom composed annotation did not work is that JUnit does not support #RunWith as a meta-annotation. Thus, when you compose your annotation as follows...
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { /* configuration classes */ })
public #interface IntegrationTests {
}
... it is JUnit that cannot see that you want to use the SpringJUnit4ClassRunner.
Spring Framework 4.0 and greater should have no problem seeing the declarations of #WebAppConfiguration and #ContextConfiguration used as meta-annotations.
In other words, the following should work for you:
#WebAppConfiguration
#ContextConfiguration(classes = { /* configuration classes */ })
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface IntegrationTests {
}
#RunWith(SpringJUnit4ClassRunner.class)
#IntegrationTests
public class MyServiceTest {
#Autowired
private MyService service;
#Test
public void myTest() {
assertNotNull(service);
}
}
As an alternative, you can use an abstract base test class as recommended by axtavt.
Regards,
Sam (author of the Spring TestContext Framework)

How to simplify two annotation into one

I want to simplify this
#Controller
#Scope("prototype")
public class Struts2ActionClass{
...
}
to this
#Struts2Action
public class Struts2ActionClass{
...
}
attempt to avoid foggetting the #Scope("prototype")
Dose anyone have any idea?
Update:
I did this copy the code of #Controller,it seems worked.
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Controller
#Scope("prototype")
public #interface Struts2Action {
}
But why?
I did this copy the code of #Controller,it seems worked.
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Controller
#Scope("prototype")
public #interface Struts2Action {
}
It works because that's the way to combine annotations. So now you don't need to write every annotation (Controller, Scope etc.), just the parent one (Struts2Action)

Categories