Spring Transactional Parameterized Test and Autowiring - java

Is there a way to get a class that extends AbstractTransactionalJUnit4SpringContexts to play nicely with JUnit's own #RunWith(Parameterized.class), so that fields marked as Autowired get wired in properly?
#RunWith(Parameterized.class)
public class Foo extends AbstractTransactionalJUnit4SpringContextTests {
#Autowired private Bar bar
#Parameters public static Collection<Object[]> data() {
// return parameters, following pattern in
// http://junit.org/apidocs/org/junit/runners/Parameterized.html
}
#Test public void someTest(){
bar.baz() //NullPointerException
}
}

See http://jira.springframework.org/browse/SPR-5292
There is a solution.

You can use a TestContextManager from Spring. In this example, I'm using Theories instead of Parameterized.
#RunWith(Theories.class)
#ContextConfiguration(locations = "classpath:/spring-context.xml")
public class SeleniumCase {
#DataPoints
public static WebDriver[] drivers() {
return new WebDriver[] { firefoxDriver, internetExplorerDriver };
}
private TestContextManager testContextManager;
#Autowired
SomethingDao dao;
private static FirefoxDriver firefoxDriver = new FirefoxDriver();
private static InternetExplorerDriver internetExplorerDriver = new InternetExplorerDriver();
#AfterClass
public static void tearDown() {
firefoxDriver.close();
internetExplorerDriver.close();
}
#Before
public void setUpStringContext() throws Exception {
testContextManager = new TestContextManager(getClass());
testContextManager.prepareTestInstance(this);
}
#Theory
public void testWork(WebDriver driver) {
assertNotNull(driver);
assertNotNull(dao);
}
}
I found this solution here : How to do Parameterized/Theories tests with Spring

You can use SpringClassRule and SpringMethodRule for this purpose
#RunWith(Parameterized.class)
#ContextConfiguration(...)
public class FooTest {
#ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
#Autowired
private Bar bar
#Parameters
public static Collection<Object[]> data() {
// return parameters, following pattern in
// http://junit.org/apidocs/org/junit/runners/Parameterized.html
}
#Test
public void someTest() {
bar.baz() //NullPointerException
}
}

No, you can't. The superclass has:
#RunWith(SpringJUnit4ClassRunner.class)
which assures that the tests are run within spring context. If you replace it, you are losing this.
What comes to my mind as an alternative is to extend SpringJunit4ClassRunner, provide your custom functionality there and use it with #RunWith(..). Thus you will have the spring context + your additional functionality. It will call super.createTest(..) and then perform additional stuff on the test.

I've had to handle the transactions programmatically (see http://www.javathinking.com/2011/09/junit-parameterized-test-with-spring-autowiring-and-transactions/):
#RunWith(Parameterized.class)
#ContextConfiguration(locations = "classpath*:/testContext.xml")
public class MyTest {
#Autowired
PlatformTransactionManager transactionManager;
private TestContextManager testContextManager;
public MyTest (... parameters for test) {
// store parameters in instance variables
}
#Before
public void setUpSpringContext() throws Exception {
testContextManager = new TestContextManager(getClass());
testContextManager.prepareTestInstance(this);
}
#Parameterized.Parameters
public static Collection<Object[]> generateData() throws Exception {
ArrayList list = new ArrayList();
// add data for each test here
return list;
}
#Test
public void validDataShouldLoadFully() throws Exception {
new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
status.setRollbackOnly();
try {
... do cool stuff here
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
});
}

Here's how I made it work in Spring Boot 1.5.7:
Add the #RunWith(Parameterized.class) annotation to your class
Inject your dependency as class field with:
#Autowired
private Bar bar;
Add your parameter/s as class fields:
private final int qux;
private final Boolean corge;
private final String grault;
Add a constructor to initialize the parameter/s as follows:
public Foo(int qux, Boolean corge, String grault) throws Exception {
this.qux = qux;
this.corge = corge;
this.grault = grault;
new TestContextManager(getClass()).prepareTestInstance(this);
}
Add a static method data that returns a Collection containing the values of your parameters at each iteration, respecting the order by which they are passed to the constructor:
#Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{ 1, Boolean.FALSE, "Hello" },
{ 2, Boolean.TRUE, null },
{ 3, null, "world" }
});
}
Write your test using the class fields declared above like follows:
#Test public void someTest(){
// Some arrangements
// Some actions
assertThat(myTestedIntValue, is(equalTo(qux));
assertThat(myTestedBooleanValue, is(equalTo(corge));
assertThat(myTestedStringValue, is(equalTo(grault));
}

Inspired by Simon's solution, you can use TestContextManager also with Parameterized runner:
#RunWith(Parameterized.class)
#ContextConfiguration(locations = "classpath:/spring-context.xml")
public class MyTestClass {
#Parameters public static Collection data() {
// return parameters, following pattern in
// http://junit.org/apidocs/org/junit/runners/Parameterized.html
}
#Before
public void setUp() throws Exception {
new TestContextManager(getClass()).prepareTestInstance(this);
}
}
Here is full example
I am not sure about handling #Transactional in this case.

Related

PowerMockito null pointer when trying to use ApplicationContext

I have a class name ServiceLocator
public class ServiceLocator implements ApplicationContextAware {
private transient ApplicationContext _applicationContext;
private static ServiceLocator _instance = new ServiceLocator();
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
_instance._applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return _instance._applicationContext;
}
public static Object findService(String serviceName) {
return _instance._applicationContext.getBean(serviceName);
}
}
I am trying to use that class to find Service into Approver class methods
public class ApproverService extends AbstractDataService implements IApproverService {
public void updateCompletedInboxStatus(String status) {
IInboxService inboxService = (IInboxService)ServiceLocator.findService("inboxService");
InboxItem inboxItem = inboxService.getInboxItem("test");
inboxItem.setWorkItemStatus(status);
inboxService.saveInboxItem(inboxItem);
}
}
With that code i am trying to write Junit with PowerMockRunner
#RunWith(PowerMockRunner.class)
#PrepareForTest({ApproverService.class})
public class ApproverServiceTest {
#InjectMocks
ApproverService approverService;
#Mock
IInboxService inboxService;
#Mock
ServiceLocator serviceLocator;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void updateCompletedInboxStatus() {
RequestAccessHeader reqHdr = new RequestAccessHeader();
reqHdr.setRequestStatus(AccessConstants.REQ_STATUS_HOLD_INT);
String status = "test";
PowerMockito.mockStatic(ServiceLocator.class);
when(serviceLocator.findService("inboxService")).thenReturn(inboxService);
approverService.updateCompletedInboxStatus(status);
}
}
But I am getting null pointer
java.lang.NullPointerException
at com.alnt.fabric.common.ServiceLocator.findService(ServiceLocator.java:25)
at com.alnt.access.approver.service.ApproverServiceTest.updateCompletedInboxStatus(ApproverServiceTest.java:80)
Please help me to find the solution for that issue.
The static method is obviously not mocked.
The problem is most probably because you haven't add the to-be-mocked class in #PrepareForTest
Change it to #PrepareForTest({ApproverService.class, ServiceLocator.class})
Off-topics:
Although it compiles, calling static method by instance reference is not a good practice. Therefore the line should be when(ServiceLocator.findService(...)).thenReturn(inboxService).
Another problem is, you tried to use Singleton pattern but in wrong way. A singleton is suppose to return you an instance so the caller can call its instance method. Your findService is preferably an instance method and to be called as ServiceLocator.getInstance().findService(...). To further improve, unless you really need it to be a singleton, you should make it a normal object instance and inject to objects that need it (given you are already using Spring, I see no reason making a Singleton)
The setup for the static method is not mocked correctly
#RunWith(PowerMockRunner.class)
#PrepareForTest({ServiceLocator.class}) //Prepare static class for mock
public class ApproverServiceTest {
#Mock
IInboxService inboxService;
#Mock
InboxItem item;
#InjectMocks
ApproverService approverService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void updateCompletedInboxStatus() {
//Arrange
String status = "test";
PowerMockito.mockStatic(ServiceLocator.class);
when(ServiceLocator.findService("inboxService")) //<-- NOTE static call
.thenReturn(inboxService);
when(inboxService.getInboxItem("test")).thenReturn(item);
//Act
approverService.updateCompletedInboxStatus(status);
//...
}
}
Reference Mocking Static Method
The subject under test should actually be refactored to avoid the service locator anit-pattern / code smell and should follow explicit dependency principle via constructor injection.
public class ApproverService extends AbstractDataService implements IApproverService {
private IInboxService inboxService;
#Autowired
public ApproverService(IInboxService inboxService){
this.inboxService = inboxService;
}
public void updateCompletedInboxStatus(String status) {
InboxItem inboxItem = inboxService.getInboxItem("test");
inboxItem.setWorkItemStatus(status);
inboxService.saveInboxItem(inboxItem);
}
}
That way the subject class is genuine about what it needs to perform its function correctly,
And the test can then be refactored accordingly
#RunWith(PowerMockRunner.class)
public class ApproverServiceTest {
#Mock
IInboxService inboxService;
#Mock
InboxItem item;
#InjectMocks
ApproverService approverService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void updateCompletedInboxStatus() {
//Arrange
String status = "test";
when(inboxService.getInboxItem("test")).thenReturn(item);
//Act
approverService.updateCompletedInboxStatus(status);
//...
}
}

Spring bean scope for "one object per test method"

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})

Mocking a class which uses static void method of another class

public class ProjectIdInitializer {
public static void setProjectId(String projectId) {
//load spring context which i want to escape in my test
}
}
public class MyService {
public Response create(){
...
ProjectIdInitializer.setProjectId("Test");
}
}
#RunWith(PowerMockRunner.class)
#PrepareForTest({ProjectIdInitializer.class})
public class MyServiceTest{
#InjectMocks
private MyService myServiceMock ;
public void testCreate() {
PowerMockito.mockStatic(ProjectIdInitializer.class);
PowerMockito.doNothing().when(ProjectIdInitializer.class, "setProjectId", Mockito.any(String.class));
// Does not work,still tries to load spring context
Response response=myServiceMock .create();
}
How can i make sure that nothing happens if ProjectIdInitializer.setProjectId() is called from myservice?
As stated in comments, you should be aware that many things might break because of PowerMock.
You need to use PowerMock runner, something like that:
#RunWith(PowerMockRunner.class)
#PrepareForTest(ProjectIdInitializer.class)
public class MyServiceTest{
private MyService myService = new MyService();
public void testCreate()
{
PowerMockito.mockStatic(ProjectIdInitializer.class);
PowerMockito.doNothing().when(ProjectIdInitializer.class, "setProjectId", Mockito.any(String.class));
Response response=myService.create();
}
}
see also this doc.
This self contained sample:
#RunWith(PowerMockRunner.class)
#PrepareForTest(A.ProjectIdInitializer.class)
public class A {
private MyService myService = new MyService();
#Test
public void testCreate() throws Exception {
PowerMockito.mockStatic(ProjectIdInitializer.class);
PowerMockito.doNothing().when(ProjectIdInitializer.class, "setProjectId", Mockito.any(String.class));
System.out.println("Before");
Response response = myService.create();
System.out.println("After");
}
public static class ProjectIdInitializer {
public static void setProjectId(String projectId) {
//load spring context which i want to escape in my test
System.out.println(">>>>>> Game over");
}
}
public static class Response {
}
public static class MyService {
public Response create() {
// ...
ProjectIdInitializer.setProjectId("Test");
return null;
}
}
}
outputs:
Before
After
As expected

Do we need to initialize #mock object

I m using mockito for junit. I have doubt while creating mock of object.
I have class called DBConnect. Where I need database properties like dbname, credentials etc.
This class is used by PatientDetails. Now when I am writing junit for PatientDetails. So I am using following code.
#RunWith(MockitoJUnitRunner.class)
public class PatientDetailsTest {
#Mock
DBConnect dbConnect
#Before
public void setUp()
{
PatientDetails testClass = new PatientDetails();
testClass.setDBConnect(dbConnect);
}
}
I can not get correct result with this.
You code seems fine to me. I've extended it slightly to include a test, so that it can be run. This works fine:
#RunWith(MockitoJUnitRunner.class)
public class PatientDetailsTest {
#Mock
DBConnect dbConnect;
#Before
public void setUp() {
when(dbConnect.sayHello()).thenReturn("works for me");
PatientDetails testClass = new PatientDetails();
testClass.setDBConnect(dbConnect);
}
#Test
public void testname() throws Exception {
System.out.println("foo");
}
private static interface DBConnect {
String sayHello();
}
private static class PatientDetails {
public void setDBConnect(DBConnect dbConnect) {
System.out.println(dbConnect.sayHello());
}
}
}
Output:
works for me
foo

How can I use the Parameterized JUnit test runner with a field that's injected using Spring?

I'm using Spring to inject the path to a directory into my unit tests. Inside this directory are a number of files that should be used to generate test data for parameterized test cases using the Parameterized test runner. Unfortunately, the test runner requires that the method that provides the parameters be static. This doesn't work for my situation because the directory can only be injected into a non-static field. Any ideas how I can get around this?
You can use a TestContextManager from Spring. In this example, I'm using Theories instead of Parameterized.
#RunWith(Theories.class)
#ContextConfiguration(locations = "classpath:/spring-context.xml")
public class SeleniumCase {
#DataPoints
public static WebDriver[] drivers() {
return new WebDriver[] { firefoxDriver, internetExplorerDriver };
}
private TestContextManager testContextManager;
#Autowired
SomethingDao dao;
private static FirefoxDriver firefoxDriver = new FirefoxDriver();
private static InternetExplorerDriver internetExplorerDriver = new InternetExplorerDriver();
#AfterClass
public static void tearDown() {
firefoxDriver.close();
internetExplorerDriver.close();
}
#Before
public void setUpStringContext() throws Exception {
testContextManager = new TestContextManager(getClass());
testContextManager.prepareTestInstance(this);
}
#Theory
public void testWork(WebDriver driver) {
assertNotNull(driver);
assertNotNull(dao);
}
}
I found this solution here : How to do Parameterized/Theories tests with Spring
I assume you are using JUnit 4.X since you mentioned the Parameterized test runner. This implies you aren't using #RunWith(SpringJUnit4ClassRunner). Not a problem, just listing my assumptions.
The following uses Spring to get the test files directory from the XML file. It doesn't inject it, but the data is still available to your test. And in a static method no less.
The only disadvantage I see is that it may mean your Spring config is getting parsed/configured multiple times. You could load just a smaller file with test specific info if need be.
#RunWith(Parameterized.class)
public class MyTest {
#Parameters
public static Collection<Object[]> data() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/jeanne/jeanne.xml");
String dir = ctx.getBean("testFilesDirectory", String.class);
// write java code to link files in test directory to the array
return Arrays.asList(new Object[][] { { 1 } });
}
// rest of test class
}
For someone reading this late 2015 or later, Spring 4.2 has, in addition to SpringJUnit4ClassRunner added SpringClassRule and SpringMethodRule which leverage the support for Spring TestContext Framework.
This means first class support for any Runner like MockitoJUnitRunner or Parameterized:
#RunWith(Parameterized.class)
public class FibonacciTest {
#ClassRule public static final SpringClassRule SCR = new SpringClassRule();
#Rule public final SpringMethodRule springMethodRule = new SpringMethodRule();
long input;
long output;
public FibonacciTest(long input, long output) { this.input = input; ...}
#Test
public void testFibonacci() {
Assert.assertEquals(output, fibonacci(input));
}
public List<Long[]> params() {
return Arrays.asList(new Long[][] { {0, 0}, {1, 1} });
}
}
It's enough to annotate test class with #RunWith(Parameterized.class) and #ContextConfiguration, use #Autowired for dependency injection and use TestContextManager in constructor for initialization, e.g.:
#RunWith(Parameterized.class)
#ContextConfiguration(classes = TestConfig.class)
public class MyTest {
#Autowired
private DataSource dataSource;
private final int param;
#Parameterized.Parameters
public static List<Object[]> params() {
return Arrays.asList(new Object[][]{
{1},
{2},
});
}
public MyTest(int p) {
this.param = p;
new TestContextManager(getClass()).prepareTestInstance(this);
}
#Test
public void testSomething() {
…
}
}
Here is a first solution without JUnit 4.12 parameterized factory, below an improved solution with it.
Static context without transactional support
Let Spring do all configuration parsing and autowiring with TestContextManager class.
The trick is to use a fake test instance to get autowired fields and pass them to the parameterized test which will effectively run.
But keep in mind prepareTestInstance() do the autowiring but doesn't manage test transaction and other nice stuffs handled by beforeTestMethod() and afterTestMethod().
#RunWith(Parameterized.class)
#ContextConfiguration(locations = {"/test-context.xml", "/mvc-context.xml"})
#WebAppConfiguration
#ActiveProfiles("test-profile")
public class MyTest {
#Parameters
public static Collection<Object[]> params() throws Exception {
final MyTest fakeInstance = new MyTest();
final TestContextManager contextManager = new TestContextManager(MyTest.class);
contextManager.prepareTestInstance(fakeInstance);
final WebApplicationContext context = fakeInstance.context;
// Do what you need with Spring context, you can even access web resources
final Resource[] files = context.getResources("path/files");
final List<Object[]> params = new ArrayList<>();
for (Resource file : files) {
params.add(new Object[] {file, context});
}
return params;
}
#Parameter
public Resource file;
#Autowired
#Parameter(1)
public WebApplicationContext context;
}
However a drawback appear if you have a lot of autowired fields because you have to manually pass them to the array parameters.
Parameterized factory with full Spring support
JUnit 4.12 introduce 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 previous test class to give full Spring support like test transaction, reinit dirty context and servlet test. And of course there no more need to pass autowired fields from fake test instance to parameterized test.
#UseParametersRunnerFactory(SpringParametersRunnerFactory.class)
#RunWith(Parameterized.class)
#ContextConfiguration(locations = {"/test-context.xml", "/mvc-context.xml"})
#WebAppConfiguration
#Transactional
#TransactionConfiguration
public class MyTransactionalTest {
#Parameters
public static Collection<Object[]> params() throws Exception {
final MyTransactionalTest fakeInstance = new MyTransactionalTest();
final TestContextManager contextManager = new TestContextManager(MyTransactionalTest.class);
contextManager.prepareTestInstance(fakeInstance);
final WebApplicationContext context = fakeInstance.context;
// Do what you need with Spring context, you can even access web resources
final Resource[] files = context.getResources("path/files");
final List<Object[]> params = new ArrayList<>();
for (Resource file : files) {
params.add(new Object[] {file});
}
return params;
}
#Parameter
public Resource file;
#Autowired
private WebApplicationContext context;
}
I use the following solution with the Parameterized.class without any problem:
http://bmocanu.ro/coding/320/combining-junit-theoriesparameterized-tests-with-spring/
#ContextConfiguration(value = "classpath:test-context.xml")
public abstract class AbstractJunitTest extends AbstractJUnit4SpringContextTests {
private static TestContextManager testContextManager = null;
private static DAOFactory daoFactory = null;
#Before
public void initApplicationContext() throws Exception {
if (testContextManager == null) {
testContextManager = new TestContextManager(getClass());
testContextManager.prepareTestInstance(this);
daoFactory = (DAOFactory)applicationContext.getBean("daoFactory");
}
}
protected DAOFactory getDaoFactory() throws Exception {
return daoFactory;
}
}
#RunWith(Parameterized.class)
public class SomeTestClass extends AbstractJunitTest {
...
}
Remember that Spring inject using #Autowired, but also with setter. So instead of using #Autowired, use the setter:
private static String directory;
public void setDirectory(String directory) {
this.directory = directory;
}
public static String getDirectory() {
return directory;
}

Categories

Resources