Make ApplicationContext dirty before and after test class - java

I have a particular class (let's say MyTest) in my Spring integration tests that is using PowerMock #PrepareForTest annotation on a Spring component: #PrepareForTest(MyComponent.class). This means that PowerMock will load this class with some modifications. The problem is, my #ContextConfiguration is defined on the superclass which is extended by MyTest, and the ApplicationContext is cached between different test classes. Now, if MyTest is run first, it will have the correct PowerMock version of MyComponent, but if not - the test will fail since the context will be loaded for another test (without #PrepareForTest).
So what I want to do is to reload my context before MyTest. I can do that via
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
But what if I also want to reload context after this test is done? So I will have clean MyComponent again without PowerMock modifications. Is there a way to do both BEFORE_CLASS and AFTER_CLASS?
For now I did it with the following hack:
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
on MyTest and then
/**
* Stub test to reload ApplicationContext before execution of real test methods of this class.
*/
#DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
#Test
public void aa() {
}
/**
* Stub test to reload ApplicationContext after execution of real test methods of this class.
*/
#DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD)
#Test
public void zz() {
}
I am wondering if there is a prettier way to do that?
As a side question, is it possible to reload only certain bean and not full context?

Is there a way to do both BEFORE_CLASS and AFTER_CLASS?
No, that is unfortunately not supported via #DirtiesContext.
However, what you're really saying is that you want a new ApplicationContext for MyTest that is identical to the context for the parent test class but only lives as long as MyTest. And... you don't want to affect the context cached for the parent test class.
So with that in mind, the following trick should do the job.
#RunWith(SpringJUnit4ClassRunner.class)
// Inherit config from parent and combine with local
// static Config class to create a new context
#ContextConfiguration
#DirtiesContext
public class MyTest extends BaseTests {
#Configuration
static class Config {
// No need to define any actual #Bean methods.
// We only need to add an additional #Configuration
// class so that we get a new ApplicationContext.
}
}
Alternative to #DirtiesContext
If you want to have a context dirtied both before and after a test class, you can implement a custom TestExecutionListener that does exactly that. For example, the following will do the trick.
import org.springframework.core.Ordered;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class DirtyContextBeforeAndAfterClassTestExecutionListener
extends AbstractTestExecutionListener {
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
testContext.markApplicationContextDirty(HierarchyMode.EXHAUSTIVE);
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
testContext.markApplicationContextDirty(HierarchyMode.EXHAUSTIVE);
}
}
You can then use the custom listener in MyTest as follows.
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.TestExecutionListeners.MergeMode;
#TestExecutionListeners(
listeners = DirtyContextBeforeAndAfterClassTestExecutionListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS
)
public class MyTest extends BaseTest { /* ... */ }
As a side question, is it possible to reload only certain bean and not full context?
No, that is also not possible.
Regards,
Sam (author of the Spring TestContext Framework)

Related

TestNG Listener equivalent for #BeforeClass, with access to context

I've spent several days looking for a way to move one of my #BeforeClass methods to listener class I can reference in xml where I define content os test suite.
Problem I'm facing is that I'm using Spring for DI, and in #BeforeClass method I add some attributes to testng context, so I can use them in other places (other listeners).
I tried using onStart(final ITestContext context) from ITestListener. But that method seems to be invoked before spring manages to create beans, and I cannot perform my operations, because all my beans are nulls.
I tried using onBeforeClass(ITestClass testClass) from IClassListener. But that method only provides ITestClass, which does not give me access to context, so I can't set my attributes.
Now I'm experimenting with onConfigurationSuccess(final ITestResult itr) from IConfigurationListener, but that requires using if statement to run my code only if configuration method name is equal to springTestContextPrepareTestInstance.
Does anyone know a better way of doing this?
[EDIT] code sample
#Component
public class CleanupHelper {
private static SomeBean someBean;
#Autowired
public CleanupHelper(SomeBean someBean){
CleanupHelper.someBean = someBean;
}
public static Object getSomething(){
return someBean.getSomething();
}
}
public class ExcludedGroupsListener implements IConfigurationListener {
#Override
public void onConfigurationSuccess(final ITestResult itr) {
if (itr.getName().contains("springTestContextPrepareTestInstance")) {
var something = CleanupHelper.getSomething();
if (something != null && someOtherCondition) {
itr.setAttribute("someObject", something);
}
}
}
}
#ContextConfiguration(classes = TestConfig.class)
public class SomeTests extends AbstractTestNGSpringContextTests {
#Test
public void someTest(){
// doSomething
}
}
#Configuration
#ComponentScan(basePackages = "com.some",
excludeFilters = #Filter(type = FilterType.REGEX, pattern = "com.some.else..*"))
public class TestConfig {
}
Above code works... unfortunately onConfigurationSuccess method is invoked after each configuration method.
Try with Annotation Transformers.
You can add it in your testng.xml like any other listener.
And in there you can do things like:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;
public class TestAnnotationTransformer implements IAnnotationTransformer {
#SuppressWarnings("rawtypes")
#Override
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
if (testMethod.getName().equals("MyTest1"))
annotation.setGroups( new String[] {"GroupA" });
if(ignoreTestDependencies)
annotation.setIgnoreMissingDependencies(true);
}
}
Just an example, but you have many things there to play with.
Just bear in mind that, as I stated in the comments, this runs before runtime, so you won't be able to change things on the go like you would do with a normal listener.

Using #PostConstruct in a test class causes it to be called more than once

I am writing integration tests to test my endpoints and need to setup a User in the database right after construct so the Spring Security Test annotation #WithUserDetails has a user to collect from the database.
My class setup is like this:
#RunWith(value = SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
public abstract class IntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private Service aService;
#PostConstruct
private void postConstruct() throws UserCreationException {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
}
#Test
public void testA() {
// Some test
}
#Test
public void testB() {
// Some test
}
#Test
public void testC() {
// Some test
}
}
However the #PostConstruct method is called for every annotated #Test, even though we are not instantiating the main class again.
Because we use Spring Security Test (#WithUserDetails) we need the user persisted to the database BEFORE we can use the JUnit annotation #Before. We cannot use #BeforeClass either because we rely on the #Autowired service: aService.
A solution I found would be to use a variable to determine if we have already setup the data (see below) but this feels dirty and that there would be a better way.
#PostConstruct
private void postConstruct() throws UserCreationException {
if (!setupData) {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
setupData = true;
}
}
TLDR : Keep your way for the moment. If later the boolean flag is repeated in multiple test classes create your own TestExecutionListener.
In JUnit, the test class constructor is invoked at each test method executed.
So it makes sense that #PostConstruct be invoked for each test method.
According to JUnit and Spring functioning, your workaround is not bad. Specifically because you do it in the base test class.
As less dirty way, you could annotate your test class with #TestExecutionListeners and provide a custom TestExecutionListener but it seem overkill here as you use it once.
In a context where you don't have/want the base class and you want to add your boolean flag in multiple classes, using a custom TestExecutionListener can make sense.
Here is an example.
Custom TestExecutionListener :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class MyMockUserTestExecutionListener extends AbstractTestExecutionListener{
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
MyService myService = testContext.getApplicationContext().getBean(MyService.class);
// ... do my init
}
}
Test class updated :
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#WithUserDetails(value = "email#address.com")
#TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS,
value=MyMockUserTestExecutionListener.class)
public abstract class IntegrationTests {
...
}
Note that MergeMode.MERGE_WITH_DEFAULTS matters if you want to merge TestExecutionListeners coming from the Spring Boot test class with TestExecutionListeners defined in the #TestExecutionListeners of the current class.
The default value is MergeMode.REPLACE_DEFAULTS.

Testing one Spring Test Class for many different set of profiles

I am working with:
Spring Framework 4.3.10
JUnit 4.12
Gradle 4.3.1
I have these two test classes
#Transactional
#RunWith(Parameterized.class)
#ContextConfiguration(classes={RootApplicationContext.class})
#ActiveProfiles(resolver=TestJdbcActiveProfilesResolver.class)
#TestExecutionListeners(listeners={LoggingTestExecutionListener.class}, mergeMode=MergeMode.MERGE_WITH_DEFAULTS)
public class PersonaServiceImplJdbcTest {
#Transactional
#RunWith(Parameterized.class)
#ContextConfiguration(classes={RootApplicationContext.class})
#ActiveProfiles(resolver=TestHibernateActiveProfilesResolver.class)
#TestExecutionListeners(listeners={LoggingTestExecutionListener.class}, mergeMode=MergeMode.MERGE_WITH_DEFAULTS)
public class PersonaServiceImplHibernateTest {
The code about the #Test methods are the same for both Test classes, breaking the DRY principle, the unique difference between these two test classes is the jdbc and Hibernate profiles working together with other such as development, mysql, it internally through each TestXXXActiveProfilesResolver class variation.
Until here I have 2 test classes, breaking the DRY principle, thinking in hierarchy I am going to get 3.
How (if is possible) use one Test class where for each interaction executes two (or more) sets of profiles such as:
jdbc,development,mysql
Hibernate,development,mysql
I already have read:
Spring Boot / JUnit, run all unit-tests for multiple profiles
But I want avoid use commands either through Maven or Gradle, it to keep the control through the TestXXXActiveProfilesResolver classes.
For JUnit 4 finally I did the following (scroll down):
#Transactional
#RunWith(Parameterized.class)
#ContextConfiguration(classes={RootApplicationContext.class})
//#ActiveProfiles() ... disable
#TestExecutionListeners(listeners={LoggingTestExecutionListener.class}, mergeMode=MergeMode.MERGE_WITH_DEFAULTS)
public abstract class PersonaServiceImplTest {
...
#Autowired
private Environment environment;
#Before
public void setup(){
logger.info("Profiles: {}", Arrays.toString(environment.getActiveProfiles()));
}
//#Test disable
public void someTest(){
assertThat(...)
}
#ActiveProfiles(resolver=TestJdbcActiveProfilesResolver.class)
public static class ForJdbc extends PersonaServiceImplTest {
public ForJdbc(Persona persona){
super(persona);
}
#Test
#Override
#Sql(scripts={"classpath:/.../script.sql"})//when be necessary
public void someTest()(){
super.someTest()();
}
}
#ActiveProfiles(resolver=TestHibernateActiveProfilesResolver.class)
public static class ForHibernate extends PersonaServiceImplTest {
public ForHibernate(Persona persona){
super(persona);
}
#Test
#Override
#Sql(scripts={"classpath:/.../script.sql"})//when be necessary
public void someTest()(){
super.someTest()();
}
...
}
}
Observations:
The outer class must be abstract
The outer class must have no #ActiveProfiles declared
The outer class has the methods to be tested, each one must have no the #Test declared
Environment is optional but is useful to let know the profiles activated for each static nested class, it through the common method annotated with #Before
Each static nested class must be public
Each static nested class must extends the outer class
Each static nested class must have #ActiveProfiles
Each static nested class overrides each test method, just to use super to call the respective overridden method
Each static nested class, for each test overridden method, it must have the #Test.
#Sql can't be reused, it must be declared for each overridden method

Configure #MockBean component before application start

I have a Spring Boot 1.4.2 application. Some code which is used during startup looks like this:
#Component
class SystemTypeDetector{
public enum SystemType{ TYPE_A, TYPE_B, TYPE_C }
public SystemType getSystemType(){ return ... }
}
#Component
public class SomeOtherComponent{
#Autowired
private SystemTypeDetector systemTypeDetector;
#PostConstruct
public void startup(){
switch(systemTypeDetector.getSystemType()){ // <-- NPE here in test
case TYPE_A: ...
case TYPE_B: ...
case TYPE_C: ...
}
}
}
There is a component which determines the system type. This component is used during startup from other components. In production everything works fine.
Now I want to add some integration tests using Spring 1.4's #MockBean.
The test looks like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
#MockBean
private SystemTypeDetector systemTypeDetectorMock;
#Before
public void initMock(){
Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
}
#Test
public void testNrOne(){
// ...
}
}
Basically the mocking works fine. My systemTypeDetectorMock is used and if I call getSystemType -> TYPE_C is returned.
The problem is that the application doesn't start. Currently springs working order seems to be:
create all Mocks (without configuration all methods return null)
start application
call #Before-methods (where the mocks would be configured)
start test
My problem is that the application starts with an uninitialized mock. So the call to getSystemType() returns null.
My question is: How can I configure the mocks before application startup?
Edit: If somebody has the same problem, one workaround is to use #MockBean(answer = CALLS_REAL_METHODS). This calls the real component and in my case the system starts up. After startup I can change the mock behavior.
In this case you need to configure mocks in a way we used to do it before #MockBean was introduced - by specifying manually a #Primary bean that will replace the original one in the context.
#SpringBootTest
class DemoApplicationTests {
#TestConfiguration
public static class TestConfig {
#Bean
#Primary
public SystemTypeDetector mockSystemTypeDetector() {
SystemTypeDetector std = mock(SystemTypeDetector.class);
when(std.getSystemType()).thenReturn(TYPE_C);
return std;
}
}
#Autowired
private SystemTypeDetector systemTypeDetector;
#Test
void contextLoads() {
assertThat(systemTypeDetector.getSystemType()).isEqualTo(TYPE_C);
}
}
Since #TestConfiguration class is a static inner class it will be picked automatically only by this test. Complete mock behaviour that you would put into #Before has to be moved to method that initialises a bean.
I was able to fix it like this
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyWebApplication.class, webEnvironment = RANDOM_PORT)
public class IntegrationTestNrOne {
// this inner class must be static!
#TestConfiguration
public static class EarlyConfiguration {
#MockBean
private SystemTypeDetector systemTypeDetectorMock;
#PostConstruct
public void initMock(){
Mockito.when(systemTypeDetectorMock.getSystemType()).thenReturn(TYPE_C);
}
}
// here we can inject the bean created by EarlyConfiguration
#Autowired
private SystemTypeDetector systemTypeDetectorMock;
#Autowired
private SomeOtherComponent someOtherComponent;
#Test
public void testNrOne(){
someOtherComponent.doStuff();
}
}
You can use the following trick:
#Configuration
public class Config {
#Bean
public BeanA beanA() {
return new BeanA();
}
#Bean
public BeanB beanB() {
return new BeanB(beanA());
}
}
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {TestConfig.class, Config.class})
public class ConfigTest {
#Configuration
static class TestConfig {
#MockBean
BeanA beanA;
#PostConstruct
void setUp() {
when(beanA.someMethod()).thenReturn(...);
}
}
}
At least it's working for spring-boot-2.1.9.RELEASE
Spring's initialization is triggered before #Before Mockito's annotation so the mock is not initialized at the time the #PostConstruct annotated method is executed.
Try to 'delay' your system detection using #Lazy annotation on the SystemTypeDetector component. Use your SystemTypeDetector where you need it, keep in mind that you cannot trigger this detection in a #PostConstruct or equivalent hook.
I think that it's due to the way you autowire your dependencies. Take a look at this (specially the part about 'Fix #1: Solve your design and make your dependencies visible'). That way you can also avoid using the #PostConstruct and just use the constructor instead.
What U are using, is good for a unit tests:
org.mockito.Mockito#when()
Try to use the following methods for mocking spring beans when the context is spined-up:
org.mockito.BDDMockito#given()
If u are using #SpyBean, then u should use another syntax:
willReturn(Arrays.asList(val1, val2))
.given(service).getEntities(any());

Spring's #Retryable not working when running JUnit Test

I have this test:
#RunWith(MockitoJUnitRunner.class)
public class myServiceTest {
#InjectMocks
myService subject;
private myService spy;
#Before
public void before() {
spy = spy(subject);
}
#Test
public void testing() {
when(spy.print2()).thenThrow(new RuntimeException()).thenThrow(new RuntimeException()).thenReturn("completed");
spy.print1();
verify(spy, times(3)).print2();
}
and then I have:
#Service("myService")
public class myService extends myAbstractServiceClass {
public String print1() {
String temp = "";
temp = print2();
return temp;
}
#Retryable
public String print2() {
return "completed";
}
}
then I have this interface(which my abstractService implements):
public interface myServiceInterface {
#Retryable(maxAttempts = 3)
String print1() throws RuntimeException;
#Retryable(maxAttempts = 3)
String print2() throws RuntimeException;
}
but, I get a runtimeexception thrown when I run the test, leading me to believe it is not retrying. Am I doing this wrong?
This is because you are not using the SpringJUnitClassRunner.
Mockito and your own classes are not taking the #Retryable annotation in account. So you rely on the implementation of Spring to do so. But your test does not activate Spring.
This is from the SpringJUnit4ClassRunner JavaDoc:
SpringJUnit4ClassRunner is a custom extension of JUnit's BlockJUnit4ClassRunner which provides functionality of the Spring TestContext Framework to standard JUnit tests by means of the TestContextManager and associated support classes and annotations.
To use this class, simply annotate a JUnit 4 based test class with #RunWith(SpringJUnit4ClassRunner.class) or #RunWith(SpringRunner.class).
You should restructure your test class at least to something like:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=MyConfig.class)
public class MyServiceTest {
#Configuration
#EnableRetry
#Import(myService.class)
public static class MyConfig {}
...
What am I doing there?
activate the Spring JUnit hook
specify the Spring context configuration class
define the spring configuration and import your service as a bean
enable the retryable annotation
Are there some other pitfalls?
Yes, you are using Mockito to simulate an exception. If you want to test this behaviour with Spring like this, you should have a look at Springockito Annotations.
But be aware of that: Springockito you will replace the spring bean completely which forces you to proxy the call of your retryable. You need a structure like: test -> retryableService -> exceptionThrowingBean. Then you can use Springockito or what ever you like e.g. ReflectionTestUtils to configure the exceptionThrowingBean with the behaviour you like.
You should reference the interface type of your service in your test: MyServiceInterface
And last but not least. There is a naming convention nearly all Java developers follow: class names have first letter of each internal word capitalized
Hope that helps.
Another way:
#EnableRetry
#RunWith(SpringRunner.class)
#SpringBootTest(classes={ServiceToTest.class})
public class RetryableTest {
#Autowired
private ServiceToTest serviceToTest;
#MockBean
private ComponentInsideTestClass componentInsideTestClass;
#Test
public void retryableTest(){
serviceToTest.method();
}
}
I think you should let Spring manage the bean, create the appropriate proxy and handle the process.
If you want to mock specific beans, you can create mocks and inject them to the service under test.
1st option could be unwrapping proxied service, creating mocks and manually injecting them:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class})
#DirtiesContext
public class TheServiceImplTest {
#Autowired
private TheService theService;
#Before
public void setUp(){
TheService serviceWithoutProxy = AopTestUtils.getUltimateTargetObject(theService);
RetryProperties mockRetryProperties = Mockito.mock(RetryProperties.class);
ReflectionTestUtils.setField(serviceWithoutProxy, "retryProperties", mockRetryProperties);
}
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
}
In this example, I mocked one bean, RetryProperties, and injected into the service. Also note that, in this approach you are modifying the test application context which is cached by Spring. This means that if you don't use #DirtiesContext, service will continue its way with mocked bean in other tests. You can read more here
Second option would be creating a test specific #Configuration and mock the depended bean there. Spring will pick up this new mocked bean instead of the original one:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {RetryConfiguration.class, TheServiceImplSecondTest.TestConfiguration.class})
public class TheServiceImplSecondTest {
#Autowired
private TheService theService;
#Test
public void shouldFetch() {
Assert.assertNotNull(theService);
}
#Configuration
static class TestConfiguration {
#Bean
public RetryProperties retryProperties() {
return Mockito.mock(RetryProperties.class);
}
}
}
In this example, we have defined a test specific configuration and added it to the #ContextConfiguration.

Categories