How to run Spring unit tests with a different JUnit Runner? - java

I'm testing an event processor in multithread. So I use the concurrent-junit of vmlens in my test case.
But I got nullpoint exceptions when I autowired beans since I was using ConcurrentTestRunner instead of SpringJunit4ClassRunner.
This is my pom
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.vmlens</groupId>
<artifactId>concurrent-junit</artifactId>
<version>1.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
Test case source code:
import com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner;
import com.anarsoft.vmlens.concurrent.junit.ThreadCount;
#RunWith(ConcurrentTestRunner.class)
#ContextConfiguration(locations = "classpath:applicationContext.xml")
public class EventListenerTest {
#Autowired
private EventStore es; //defined in applicationContext.xml
#Autowired
private EntityAppender ea; //a #Component
......
#Test
#ThreadCount(10)
public final void testDefaultListener() {
Long bef = es.countStoredEvents();// nullpoint exception
TestEntity1 t1 = ea.appWithDefaultListener();// nullpoint exception
......
}
}
Obviously, beans were not injected correctly.
Is there any way to fix this? Should I extend AbstractJUnit4SpringContextTests?
Attached latest code here:
EventStore is a Jpa repository:
public interface EventStore extends JpaRepository<DomainEvent, Long>{};
applicationContext.xml
<aop:config proxy-target-class="true" />
<context:annotation-config />
<jpa:repositories base-package="com.my"></jpa:repositories>
EntityAppender is defined just for test.
#Component
public class EntityAppender {
#Autowired
private TestEntity1Repository myRepository; //another Jpa repository
public EntityAppender() {
super();
}
#Transactional
public TestEntity1 appWithDefaultListener() {
TestEntity1 t1 = new TestEntity1(UUID.randomUUID().toString().replaceAll("-", ""), "aaaaaaaaaaaa", 44,
LocalDate.now());
return myRepository.save(t1);
}
...
}
Test case:
import com.anarsoft.vmlens.concurrent.junit.ConcurrentTestRunner;
import com.anarsoft.vmlens.concurrent.junit.ThreadCount;
#RunWith(ConcurrentTestRunner.class)
#ContextConfiguration(locations = "classpath:applicationContext.xml")
public class EventListenerTest {
#ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Autowired
private EventStore es;
#Autowired
private EntityAppender ea;
......
#Before
public void setUp() throws Exception {
bef = es.count(); //<====nullpoint exception due to es being null here
}
#Test
#ThreadCount(10)
public final void testDefaultListener() {
bef = es.count(); //<====== es worked well here
TestEntity1 t1 = ea.appWithDefaultListener();
......
}
}

Since Spring 4.2, when you need to provide your own JUnit Runner for some other reason (eg. ConcurrentTestRunner or Mockito's MockitoJUnitRunner), Spring provides a separate mechanism to initialize the unit test's ApplicationContext and meta configuration.
This mechanism is a combination of two JUnit 4 rules. This is documented in the official documentation, here.
The org.springframework.test.context.junit4.rules package provides the
following JUnit 4 rules (supported on JUnit 4.12 or higher).
SpringClassRule
SpringMethodRule
SpringClassRule is a JUnit TestRule that supports class-level features
of the Spring TestContext Framework; whereas, SpringMethodRule is a
JUnit MethodRule that supports instance-level and method-level
features of the Spring TestContext Framework.
Your unit test class should minimally contain
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
Here's a complete example that utilizes MockitoJUnitRunner while still making use of Spring's TestContext framework:
#ContextConfiguration(classes = ConfigExample.class)
#RunWith(MockitoJUnitRunner.class)
public class Example {
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Autowired
private Foo foo;
#Mock
private Runnable runnable;
#Test
public void method2() {
Mockito.doNothing().when(runnable).run();
foo.bar(runnable);
Mockito.verify(runnable).run();
}
}
#Configuration
class ConfigExample {
#Bean
public Foo Foo() {
return new Foo();
}
}
class Foo {
public void bar(Runnable invoke) {
invoke.run();
}
}

Related

Java / Spring: Cannot test Caching in Unit Test

In my Java application I am trying to test the following service method with a Unit Test:
#Service
#EnableCaching
#RequiredArgsConstructor
public class LabelServiceImpl implements LabelService {
private static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
#Override
public List<LabelDTO> findByUuid(UUID uuid) {
final Label label = labelRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
final List<LabelDTO> labelList = labelRepository.findByUuid(uuid);
return labelList;
}
}
Here is my Unit Test:
#Import({CacheConfig.class, LabelServiceImpl.class})
#ExtendWith(SpringExtension.class)
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
#RunWith(MockitoJUnitRunner.class)
public class CachingTest {
#InjectMocks
private LabelServiceImpl labelService;
#Autowired
private CacheManager cacheManager;
#Mock
private LabelRepository labelRepository;
#Test
void givenRedisCaching_whenFindUuid_thenReturnFromCache() {
//code omitted
LabelDTO labelFromDb = labelService.findByUuid(uuid);
LabelDTO labelFromCache = labelService.findByUuid(uuid);
verify(labelRepository, times(1)).findByUuid(uuid);
}
}
When I use #Autowired for LabelServiceImpl in the Unit Test, I get "Nullpointer exception" error. If I use #InjectMocks annotation for that instance, then there is no error, but no caching is done (it calls labelService.findByUuid 2 times instead of 1).
I think I made a mistake related to the annotations in Unit Test class, but I tried many different combinations and cannot solve the problem.
So, how can I fix the problem?
Update: Finally I fixed the problem by using the following approach. However, I also had to update the mockito-core version from 3.3.3 to 3.7.0 as shown below. Otherwise I was getting "java.lang.NoSuchMethodError: org.mockito.MockitoAnnotations.openMocks(Ljava/lang/Object;)Ljava/lang/AutoCloseable;" error.
pom.xml:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.7.0</version>
<scope>test</scope>
</dependency>
CachingTest:
#ExtendWith(MockitoExtension.class)
#SpringBootTest
public class CachingTest {
#Autowired
#InjectMocks
private LabelServiceImpl labelService;
#MockBean
private LabelRepository labelRepository;
#Test
public void givenRedisCaching_whenFindUuid_thenReturnFromCache() {
//code omitted
LabelDTO labelFromDb = labelService.findByUuid(uuid);
LabelDTO labelFromCache = labelService.findByUuid(uuid);
verify(labelRepository, times(1)).findByUuid(uuid);
assertEquals(labelFromDb, labelFromCache);
}
}
Mockito does not simulate your springt boot application. It just mocks the annotated objects and injects your mock objects into your labelService.
On the other hand to test a spring boot application #Autowired is not enough.
You have to build a Spring Boot Integration Test.
To do this annotate your Test with #SpringBootTest.
This initializes a real Application Context.
try to autowire, LabelService
package com.demo;
import org.springframework.stereotype.Service;
#Service
public class LabelService
{
public String getLabel()
{
return "Test Label " + System.currentTimeMillis();
}
}
package com.demo;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
public class CachingTest
{
#Autowired
private LabelService labelService;
#Test
public void testLabelService()
{
assertNotNull(labelService);
}
}
this works

How to mock object created by spring application context using Mockito for Unit Test coverage?

Application scenario:
TaskScheduler is triggered at regular cron interval. Each scheduler job will create multiple runnable threads. Each thread will perform the ETL process.
public class LoadTask {
private static final AuditLogService AUDIT_LOG_SERVICE =
ApplicationContextUtils.getApplicationContext().getBean("auditLogService", AuditLogService.class);
public void perform(ETLDetailsDTO etlDetailsDTO) {
// Business logic
AUDIT_LOG_SERVICE.save(AuditLogBuilder.buildSuccessAuditLog())
}
}
AuditLogService is a Service annotated spring managed bean.
#Component
public class ApplicationContextUtils implements ApplicationContextAware {
#Getter
private static ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext appContext) {
applicationContext = appContext;
}
}
LoadTask is not and spring managed component, thus I am taking the help of ApplicationContextUtils to get the bean of AuditLogService. The application is working fine. While writing the unit test case of LoadTask, Null Pointer Exception is thrown.
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class LoadTaskTest {
#InjectMocks
private LoadTask loadTask;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testPerform() {
Mockito.when(ApplicationContextUtils.getApplicationContext().getBean("auditLogService", AuditLogService.class)).thenReturn(new AuditLogService());
loadTask.perform(MockObjectHelper.getETLDetailsDTO());
}
}
I am not able to set mock object of AUDIT_LOG_SERVICE and run test case successfully. How to mock or spy ApplicationContextUtils so that LoadTask does not throw any error?
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class LoadTaskTest {
#InjectMocks
private LoadTask loadTask;
#Mock
private ApplicationContext applicationContext;
#Mock
private AuditLogService auditLogService;
#BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
new ApplicationContextUtils().setApplicationContext(applicationContext);
}
#Test
public void testPerform() {
Mockito.when(applicationContext.getBean("auditLogService", AuditLogService.class)).thenReturn(auditLogService);
loadTask.perform(MockObjectHelper.getETLDetailsDTO());
}
}
Add mockito-inline dependency
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.10.0</version>
<scope>test</scope>
</dependency>

Spring Boot Unit Test

I am new to spring boot. Need some suggestions
Here my unit test class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = DemoApplication.class)
public class EmployeeRepositoryTest {
#Autowired
protected EmployeeRepository employeeRepository;
#Test
public void insertEmploee(){
Employee employee = new Employee();
employee.setEmpName("Azad");
employee.setEmpDesignation("Engg");
employee.setEmpSalary(12.5f);
employeeRepository.save(employee);
}
}
When I run it I get exception as
java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotationAttributes(Ljava/lang/reflect/AnnotatedElement;Ljava/lang/String;ZZ)Lorg/springframework/core/annotation/AnnotationAttributes;
at org.springframework.test.util.MetaAnnotationUtils$AnnotationDescriptor.<init>(MetaAnnotationUtils.java:290)
at org.springframework.test.util.MetaAnnotationUtils$UntypedAnnotationDescriptor.<init>(MetaAnnotationUtils.java:365)
at org.springframework.test.util.MetaAnnotationUtils$UntypedAnnotationDescriptor.<init>(MetaAnnotationUtils.java:360)
at org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptorForTypes(MetaAnnotationUtils.java:191)
at org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptorForTypes(MetaAnnotationUtils.java:198)
at
Process finished with exit code -1
It seems that your problem is solved (mixing the Spring dependency versions) but let me just expand the comment from #g00glen00b on how to write unit tests.
Make sure the following dependency is in your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
As pointed out in the comment, #RunWith(SpringJUnit4ClassRunner.class) causes the unit test to start the whole application and it is used rather for integration testing.
Fortunately, Spring-boot has built in dependency for Mockito which is just what you need for unit tests like this.
Now, your unit test could look something like this:
public class EmployeeRepositoryTest {
#InjectMocks
private EmployeeRepository employeeRepository;
#Mock
private Something something; // some class that is used inside EmployRepository (if any) and needs to be injected
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void insertEmploee(){
Employee employee = new Employee();
employee.setEmpName("Azad");
employee.setEmpDesignation("Engg");
employee.setEmpSalary(12.5f);
employeeRepository.save(employee);
Mockito.verify(...); // verify what needs to be verified
}
}
Nice post about using Mockito can be found, for example, here.
Instead of using #Autowired on EmployeeRepository we can use #MockBean cause we are writing unit tests we don't need to deal with the real data we just need to verify that the function is working fine or not. Check the below code
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = Main.class)//main springboot class
#WebAppConfiguration
public abstract class AbstractBaseTest {
protected MockMvc mvc;
#Autowired
WebApplicationContext webApplicationContext;
protected void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
}
public class EmployeeRepositoryTest extends AbstractBaseTest{
#MockBean
protected EmployeeRepository employeeRepository;
#Override
#Before
public void setUp() {
super.setUp();
}
#Test
public void insertEmploee(){
Employee employee = new Employee();
employee.setEmpName("Azad");
employee.setEmpDesignation("Engg");
employee.setEmpSalary(12.5f);
Mockito.doNothing().when(employeeRepository).save(Mockito.any(Employee.class));
employeeRepository.save(employee);
Mockito.verify(employeeRepository, Mockito.times(1)).save(employee);
}
}

How do you configure Embedded MongDB for integration testing in a Spring Boot application?

I have a fairly simple Spring Boot application which exposes a small REST API and retrieves data from an instance of MongoDB. Queries to the MongoDB instance go through a Spring Data based repository. Some key bits of code below.
// Main application class
#EnableAutoConfiguration(exclude={MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
#ComponentScan
#Import(MongoConfig.class)
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
// Product repository with Spring data
public interface ProductRepository extends MongoRepository<Product, String> {
Page<Product> findAll(Pageable pageable);
Optional<Product> findByLineNumber(String lineNumber);
}
// Configuration for "live" connections
#Configuration
public class MongoConfig {
#Value("${product.mongo.host}")
private String mongoHost;
#Value("${product.mongo.port}")
private String mongoPort;
#Value("${product.mongo.database}")
private String mongoDatabase;
#Bean(name="mongoClient")
public MongoClient mongoClient() throws IOException {
return new MongoClient(mongoHost, Integer.parseInt(mongoPort));
}
#Autowired
#Bean(name="mongoDbFactory")
public MongoDbFactory mongoDbFactory(MongoClient mongoClient) {
return new SimpleMongoDbFactory(mongoClient, mongoDatabase);
}
#Autowired
#Bean(name="mongoTemplate")
public MongoTemplate mongoTemplate(MongoClient mongoClient) {
return new MongoTemplate(mongoClient, mongoDatabase);
}
}
#Configuration
#EnableMongoRepositories
public class EmbeddedMongoConfig {
private static final String DB_NAME = "integrationTest";
private static final int DB_PORT = 12345;
private static final String DB_HOST = "localhost";
private static final String DB_COLLECTION = "products";
private MongodExecutable mongodExecutable = null;
#Bean(name="mongoClient")
public MongoClient mongoClient() throws IOException {
// Lots of calls here to de.flapdoodle.embed.mongo code base to
// create an embedded db and insert some JSON data
}
#Autowired
#Bean(name="mongoDbFactory")
public MongoDbFactory mongoDbFactory(MongoClient mongoClient) {
return new SimpleMongoDbFactory(mongoClient, DB_NAME);
}
#Autowired
#Bean(name="mongoTemplate")
public MongoTemplate mongoTemplate(MongoClient mongoClient) {
return new MongoTemplate(mongoClient, DB_NAME);
}
#PreDestroy
public void shutdownEmbeddedMongoDB() {
if (this.mongodExecutable != null) {
this.mongodExecutable.stop();
}
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestProductApplication.class)
#IntegrationTest
#WebAppConfiguration
public class WtrProductApplicationTests {
#Test
public void contextLoads() {
// Tests empty for now
}
}
#EnableAutoConfiguration(exclude={MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
#ComponentScan
#Import(EmbeddedMongoConfig.class)
public class TestProductApplication {
public static void main(String[] args) {
SpringApplication.run(TestProductApplication.class, args);
}
}
So the idea here is to have the integration tests (empty at the moment) connect to the embedded mongo instance and not the "live" one. However, it doesn't work. I can see the tests connecting to the "live" instance of Mongo, and if I shut that down the build simply fails as it is still attempting to connect to the live instance of Mongo. Does anyone know why this is? How do I get the tests to connect to the embedded instance?
Since Spring Boot version 1.3 there is an EmbeddedMongoAutoConfiguration class which comes out of the box. This means that you don't have to create a configuration file at all and if you want to change things you still can.
Auto-configuration for Embedded MongoDB has been added. A dependency on de.flapdoodle.embed:de.flapdoodle.embed.mongo is all that’s necessary to get started. Configuration, such as the version of Mongo to use, can be controlled via application.properties. Please see the documentation for further information. (Spring Boot Release Notes)
The most basic and important configuration that has to be added to the application.properties files is spring.data.mongodb.port=0 (0 means that it will be selected randomly from the free ones)
for more details check: Spring Boot Docs MongoDb
EDIT: see magiccrafter's answer for Spring Boot 1.3+, using EmbeddedMongoAutoConfiguration.
If you can't use it for any reason, keep reading.
Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {
Application.class,
TestMongoConfig.class // <--- Don't forget THIS
})
public class GameRepositoryTest {
#Autowired
private GameRepository gameRepository;
#Test
public void shouldCreateGame() {
Game game = new Game(null, "Far Cry 3");
Game gameCreated = gameRepository.save(game);
assertEquals(gameCreated.getGameId(), gameCreated.getGameId());
assertEquals(game.getName(), gameCreated.getName());
}
}
Simple MongoDB repository:
public interface GameRepository extends MongoRepository<Game, String> {
Game findByName(String name);
}
MongoDB test configuration:
import com.mongodb.Mongo;
import com.mongodb.MongoClientOptions;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodProcess;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
#Configuration
public class TestMongoConfig {
#Autowired
private MongoProperties properties;
#Autowired(required = false)
private MongoClientOptions options;
#Bean(destroyMethod = "close")
public Mongo mongo(MongodProcess mongodProcess) throws IOException {
Net net = mongodProcess.getConfig().net();
properties.setHost(net.getServerAddress().getHostName());
properties.setPort(net.getPort());
return properties.createMongoClient(this.options);
}
#Bean(destroyMethod = "stop")
public MongodProcess mongodProcess(MongodExecutable mongodExecutable) throws IOException {
return mongodExecutable.start();
}
#Bean(destroyMethod = "stop")
public MongodExecutable mongodExecutable(MongodStarter mongodStarter, IMongodConfig iMongodConfig) throws IOException {
return mongodStarter.prepare(iMongodConfig);
}
#Bean
public IMongodConfig mongodConfig() throws IOException {
return new MongodConfigBuilder().version(Version.Main.PRODUCTION).build();
}
#Bean
public MongodStarter mongodStarter() {
return MongodStarter.getDefaultInstance();
}
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>1.48.0</version>
<scope>test</scope>
</dependency>
In version 1.5.7 use just this:
#RunWith(SpringRunner.class)
#DataMongoTest
public class UserRepositoryTests {
#Autowired
UserRepository repository;
#Before
public void setUp() {
User user = new User();
user.setName("test");
repository.save(user);
}
#Test
public void findByName() {
List<User> result = repository.findByName("test");
assertThat(result).hasSize(1).extracting("name").contains("test");
}
}
And
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
I'll complete previous answer
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.2.RELEASE</version>
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>${embedded-mongo.version}</version>
</dependency>
MongoConfig
#Configuration
#EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
public class MongoConfig{
}
Make sure you are explicit with your #ComponentScan. By default,
If specific packages are not defined scanning will occur from the
package of the class with this annotation. (#ComponentScan Javadoc)
Therefore, if your TestProductApplication and ProductApplication configurations are both in the same package, it is possible Spring is component-scanning your ProductApplication configuration and using that.
Additionally, I would recommend putting your Test mongo beans into a 'test' or 'local' profile and using the #ActiveProfiles annotation in your test class to enable the test/local profile.

How to run JUnit SpringJUnit4ClassRunner with Parametrized?

The following code is invalid due to duplicate #RunWith annotation:
#RunWith(SpringJUnit4ClassRunner.class)
#RunWith(Parameterized.class)
#SpringApplicationConfiguration(classes = {ApplicationConfigTest.class})
public class ServiceTest {
}
But how can I use these two annotations in conjunction?
You can use SpringClassRule and SpringMethodRule - supplied with Spring
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
#RunWith(Parameterized.class)
#ContextConfiguration(...)
public class MyTest {
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
...
There are at least 2 options to do that:
Following http://www.blog.project13.pl/index.php/coding/1077/runwith-junit4-with-both-springjunit4classrunner-and-parameterized/
Your test needs to look something like this:
#RunWith(Parameterized.class)
#ContextConfiguration(classes = {ApplicationConfigTest.class})
public class ServiceTest {
private TestContextManager testContextManager;
#Before
public void setUpContext() throws Exception {
//this is where the magic happens, we actually do "by hand" what the spring runner would do for us,
// read the JavaDoc for the class bellow to know exactly what it does, the method names are quite accurate though
this.testContextManager = new TestContextManager(getClass());
this.testContextManager.prepareTestInstance(this);
}
...
}
There is a github project https://github.com/mmichaelis/spring-aware-rule, which builds on previous blog, but adds support in a generalized way
#SuppressWarnings("InstanceMethodNamingConvention")
#ContextConfiguration(classes = {ServiceTest.class})
public class SpringAwareTest {
#ClassRule
public static final SpringAware SPRING_AWARE = SpringAware.forClass(SpringAwareTest.class);
#Rule
public TestRule springAwareMethod = SPRING_AWARE.forInstance(this);
#Rule
public TestName testName = new TestName();
...
}
So you can have a basic class implementing one of the approaches, and all tests inheriting from it.
There is another solution with JUnit 4.12 without the need of Spring 4.2+.
JUnit 4.12 introduces ParametersRunnerFactory which allow to combine parameterized test and Spring injection.
public class SpringParametersRunnerFactory implements ParametersRunnerFactory {
#Override
public Runner createRunnerForTestWithParameters(TestWithParameters test) throws InitializationError {
final BlockJUnit4ClassRunnerWithParameters runnerWithParameters = new BlockJUnit4ClassRunnerWithParameters(test);
return new SpringJUnit4ClassRunner(test.getTestClass().getJavaClass()) {
#Override
protected Object createTest() throws Exception {
final Object testInstance = runnerWithParameters.createTest();
getTestContextManager().prepareTestInstance(testInstance);
return testInstance;
}
};
}
}
The factory can be added to test class to give full Spring support like test transaction, reinit dirty context and servlet test.
#UseParametersRunnerFactory(SpringParametersRunnerFactory.class)
#RunWith(Parameterized.class)
#ContextConfiguration(locations = {"/test-context.xml", "/mvc-context.xml"})
#WebAppConfiguration
#Transactional
#TransactionConfiguration
public class MyTransactionalTest {
#Autowired
private WebApplicationContext context;
...
}
If you need Spring context inside #Parameters static method to provide parameters to test instances, please see my answer here How can I use the Parameterized JUnit test runner with a field that's injected using Spring?.
Handle application context by yourself
What worked for me was having a #RunWith(Parameterized.class) test class that managed the application context "by hand".
To do that I created an application context with the same string collection that would be in the #ContextConfiguration. So instead of having
#ContextConfiguration(locations = { "classpath:spring-config-file1.xml",
"classpath:spring-config-file2.xml" })
I had
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {
"classpath:spring-config-file1.xml", "classpath:spring-config-file2.xml" });
And for each #Autowired I needed I fetched it by hand from the created context:
SomeClass someBean = ctx.getBean("someClassAutowiredBean", SomeClass.class);
Do not forget to close the context at the end:
((ClassPathXmlApplicationContext) ctx).close();

Categories