Unit-test a method that is Advised by #Around advice - java

I'm currently writing JUnit unit-tests for a class in an Application that uses Spring and AspectJ. The class under test has a couple public methods that are advised by an around-advice method in an aspect class. The Aspect has a couple of injected fields that turn up null when the advised method is executed, even though I've successfully instantiated those beans in the test application context, and when their methods are called, they throw nullpointerexceptions. Here's a simplified version of the code:
The class to be tested:
public class ClassUnderTest {
#Inject
private Foo foo;
#Audit(StringValue="arg", booleanValue=true)
public Object advisedMethod() {
Object ret = new Object();
//things happen
return ret;
}
The Aspect:
#Aspect
#Configurable
public class AuditAspect implements Versionable {
#Inject
Foo foo;
#Inject
Bar bar;
#Around("#annotation(Audit)")
public Object aroundAdvice(ProceedingJoinPoint pjp, Audit audit) {
// Things happen
privMethod(arg);
// Yet other things happen
Object ret = pjp.proceed();
// Still more things happen
return ret;
}
private Object privMethod(Object arg) {
// Things continue to happen.
// Then this throws a NullPointerException because bar is null.
bar.publicBarMethod(arg2);
// Method continues ...
}
}
The Audit interface:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Audit {
String value() default "";
boolean bool() default false;
}
The Config file that provides the application context:
import static org.mockito.Mockito.*;
#Configuration
public class ClassUnderTestTestConfig {
#Bean
Foo foo() {
return mock(Foo.class);
}
#Bean
Bar bar() {
return mock(Bar.class);
}
#Bean
ClassUnderTest classUnderTest() {
return new ClassUnderTest();
}
#Bean
#DependsOn({"foo", "bar"})
Aspect aspect() {
return new Aspect();
}
}
The Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextHierarchy({
#ContextConfiguration(classes = ClassUnderTestTestConfiguration.class),
#ContextConfiguration(classes = ClassUnderTest.class)
})
public class ClassUnderTestTest {
private static final String sessionNumber = "123456";
#Inject
Foo foo;
#Inject
Bar bar;
#Inject
ClassUnderTest classUnderTest;
#Inject
Aspect aspect;
#Test
public void test() {
// This call triggers the advice which ends up throwning
// a NullPointerException.
classUnderTest.advised();
}
}
I've also tried making my own Spring proxy and then adding the aspect to it manually as advised in this stack overflow post, by adding this code to the test class:
#Before
public void setUp() {
DataPointRestWebService target = new DataPointRestWebService();
AspectJProxyFactory proxyMaker = new AspectJProxyFactory(target);
proxyMaker.addAspect(auditAspect);
dataPointRestWebService = proxyMaker.getProxy();
}
However that ends up throwing:
AopConfigException: Advice must be declared inside an aspect type. Offending method 'public java.lang.Object Aspect.aroundAdvice' in class [Aspect]
I find this cryptic because I the Aspect class does have the #Aspect annotation before it, and the class works outside of a test environment.
I'm very new to Spring and AspectJ, so I'm completely open to the notion that I'm going about this all wrong. I've provided dummy code here in hopes of leaving out unhelpful specifics, but also because the working code is proprietary and not mine. If you think I've left out an important detail, let me know and I'll try to add it.
Thanks in advance for any help, and please let me know if I'm leaving out any crucial information.
EDIT:
By request, I've added the full NullPointerException stack trace:
java.lang.NullPointerException
at com.unifiedcontrol.aspect.AuditAspect.getCaller(AuditAspect.java:265)
at com.unifiedcontrol.aspect.AuditAspect.ajc$inlineAccessMethod$com_unifiedcontrol_aspect_AuditAspect$com_unifiedcontrol_aspect_AuditAspect$getCaller(AuditAspect.java:1)
at com.unifiedcontrol.aspect.AuditAspect.aroundAuditAdvice(AuditAspect.java:79)
at com.unifiedcontrol.server.rest.DataPointRestWebServiceTest.dummyTest(DataPointRestWebServiceTest.java:109)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:233)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:87)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:176)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Where AuditAspect == Aspect and DataPointRestWebService == ClassUnderTest.

Here's what ended up solving our problem:
We changed the following method in the ClassUnderTestTestConfig class:
#Bean
#DependsOn({"foo", "bar"})
Aspect aspect() {
return new Aspect();
}
to:
#Bean
#DependsOn({"foo", "bar"})
Aspect aspect() {
return Aspects.aspectOf(Aspect.class);
}
for which we added the following import statement:
import org.aspectj.lang.Aspects;
The original code successfully returned a new Aspect object, however when ClassUnderTest.advisedMethod() was called, the jointpoint was delegated to a different Aspect object that hadn't been injected with non-null foo and bar member. Something about how the Aspects.aspectOf() method works ensures that the Aspect object created by TestConfig is the one that provides advice to the call to advisedMethod().
At the moment I have no idea why this solved the problem. Someone else at work found the solution. I plan to look into it and edit this post with more information, but in the mean time all contributions are welcome.

Related

Best practices on how to test a void returning method in JUnit and Spring?

I am not sure if I am testing a void returning method the correct way and also if my class-under-test (cut) requires any change in order to make it 100% testable and bug-proof.
I am seeing NullPointerException while executing the test because loginOperations is not getting set.
Error:
java.lang.NullPointerException
at com.demo.service.LoginService.doLogin(LoginService.java:40)
at com.demo.service.LoginServiceTest.doLogin(LoginServiceTest.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
LoginService.java
#Service
public class LoginService {
#Autowired
private ILoginOperations loginOperations;
public void doLogin(HttpServletRequest request, String result) {
LoginDTO loginDTO = new LoginDTO(request.getParameter("username"), result);
loginOperations.doLogin(loginDTO);
}
}
LoginServiceTest.java
public class LoginServiceTest {
private LoginService instance = new LoginService();
ILoginOperations loginOperations = Mockito.mock(ILoginOperations.class);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
String result = "some string";
#Test
public void doLogin() {
when(request.getParameter("username")).thenReturn("johndoe");
instance.doLogin(request, result); //throws NPE while calling loginOperations.doLogin() because
assertNotNull(instance); //IS THIS THE CORRECT WAY TO TEST A VOID RETURNING METHOD ???
}
}
Now, there are 2 ways to fix the test.
I can fix the class-under-test by adding a setter method for loginOperations class and call that setter method in the test
Change #Test public void doLogin() { to #Test(expected = Exception.class) public void doLogin() {
Not sure which one is the best practice above and why.
Another Question:
Other question that I have is how to assert on a method that returns nothing. There is something like verify() but not sure how to use it.
1.You can fix the test case by adding setter method in LoginService or you can use constructor injection like -
#Autowired
public LoginService(ILoginOperations loginOperations) {
this.loginOperations = loginOperations;
}
Validating exception as #Test(expected = Exception.class) public void doLogin() is certainly not a good idea as doLogin method does not throw exception in normal circumstance.
The better way to test method with void return type is using verification API (example - mockito verification API example). You can also use Mockito's ArgumentCaptor to capture argument and assert state of that argument, along with verification API as -
#RunWith(MockitoJUnitRunner.class)
public class LoginServiceTest {
#Captor
private ArgumentCaptor<LoginDTO> captor;
#Mock
private ILoginOperations loginOperations;
#Mock
private HttpServletRequest mockServletRequest;
#InjectMocks
private LoginService loginService;
#Test
public void validateLogin() {
when(mockServletRequest.getParameter("username")).thenReturn("mock_user_name");
loginService.doLogin(mockServletRequest, "mock_result");
verify(loginOperations).doLogin(captor.capture());
LoginDTO expectedLoginDTO = captor.getValue();
assertThat(expectedLoginDTO.getResult(), is("mock_result"));
assertThat(expectedLoginDTO.getUsername(), is("mock_user_name"));
}
}
There is an excellent article from Martin Fowler about this method of testing - Mocks Aren't Stubs
actually you should create a constructor for your LoginService that gets the ILoginOperations, in this way you can create the LoginService in your test class and pass the mocked ILoginOperations as parameter, all this stuff should be done in a #Before method.
Or you can try with #InjectMocks for your LoginService and have your ILoginOperations annotated as #Mock.

How to use EasyMock objects in JUnit #Before method as well as test method

I am attempting to use EasyMock alongside JUnit and have run into difficulties while scheduling method calls on a mocked dependency in a JUnit 4 #Before method.
In the below example the test class MockWithBeforeTest is testing the class ClassUnderTest. Dependency is passed to ClassUnderTest's constructor, in which one of Dependency's methods is called, returning a value needed to initialise ClassUnderTest. This process of initialising ClassUnderTest will be the same for all tests, so I decorate the ClassUnderTest#setUp method with a JUnit 4 #Before annotation.
When testing the method ClassUnderTest#getDerived we expect a call to the mocked Dependency instance to return a value, which we schedule in the method MockWithBeforeTest#testGetDerived. However, this test unexpectedly fails with the error Unexpected method call Dependency.getB() despite the fact that this call is scheduled in MockWithBeforeTest#testGetDerived.
How should I modify the example code such that MockWithBeforeTest#testGetDerived passes?
Example code
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import org.easymock.EasyMockRule;
import org.easymock.Mock;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class MockWithBeforeTest {
#Rule
public EasyMockRule rule = new EasyMockRule(this);
#Mock
private Dependency dependency;
private ClassUnderTest classUnderTest;
#Before
public void setUp() {
expect(this.dependency.getA()).andReturn(2);
replay(this.dependency);
this.classUnderTest = new ClassUnderTest(this.dependency);
verify(this.dependency);
}
#Test
public void testGetDerived() {
expect(this.dependency.getB()).andReturn(3);
replay(this.dependency);
assertEquals(6, this.classUnderTest.getDerived(1));
verify(this.dependency);
}
}
class ClassUnderTest {
private int a;
private Dependency dependency;
ClassUnderTest(Dependency dependency) {
this.a = dependency.getA();
this.dependency = dependency;
}
void setA(int val) {
this.a = val;
}
int getDerived(int val) {
return val * this.a * this.dependency.getB();
}
}
class Dependency {
private int a;
private int b;
Dependency(int a, int b) {
this.a = a;
this.b = b;
}
int getA() {
return this.a;
}
int getB() {
return this.b;
}
}
Stack Trace
java.lang.AssertionError:
Unexpected method call Dependency.getB():
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:44)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:101)
at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:97)
at Dependency$$EnhancerByCGLIB$$6d3a4341.getB(<generated>)
at MockWithBeforeTest.testGetDerived(MockWithBeforeTest.java:33)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.easymock.internal.EasyMockStatement.evaluate(EasyMockStatement.java:43)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Why not create an instance of Dependency to pass to the constructor?
The example given above is representative of a general problem in which the dependency passed to the class under test is much more complex than Dependency.
Speculation about design problem
I am mindful of the fact that details of the class under test's implementation are leaking into the test class via the scheduled methods on the mocked dependency. However I am not experienced enough with mocking frameworks to tell if this is an unavoidable side effect of mocking, or a symptom of a flaw in my design. Any guidance on this would be appreciated.
Software version information
Java: 1.8.0_201
JUnit: 4.12
EasyMock: 4.2
More research and discussion with colleagues produced a solution. The step I missed is to reset the mocked Dependency object using EasyMock.reset(this.dependency) to allow additional expected calls to be added in the test methods. The fixed MockWithBeforeTest is
public class MockWithBeforeTest {
#Rule
public EasyMockRule rule = new EasyMockRule(this);
#Mock
private Dependency dependency;
private ClassUnderTest classUnderTest;
#Before
public void setUp() {
expect(this.dependency.getA()).andReturn(2);
replay(this.dependency);
this.classUnderTest = new ClassUnderTest(this.dependency);
verify(this.dependency);
reset(this.dependency); // Allow additional expected method calls to be specified
// in the test methods
}
#Test
public void testGetDerived() {
expect(this.dependency.getB()).andReturn(3);
replay(this.dependency);
assertEquals(6, this.classUnderTest.getDerived(1));
verify(this.dependency);
}
}
The replay() must be called only once, after everything is recorded. That's why it doesn't work here.
Since, in your case you are using the mock in the constructor, you need to instantiate the tested class after the replay.
public class MockWithBeforeTest {
#Rule
public EasyMockRule rule = new EasyMockRule(this);
#Mock
private Dependency dependency;
private ClassUnderTest classUnderTest;
#Before
public void setUp() {
expect(this.dependency.getA()).andReturn(2);
}
#Test
public void testGetDerived() {
expect(this.dependency.getB()).andReturn(3);
replay(this.dependency);
this.classUnderTest = new ClassUnderTest(this.dependency);
assertEquals(6, this.classUnderTest.getDerived(1));
verify(this.dependency);
}
}
You could also reset the mock in between but I'm not sure I like that. It will let you pass the constructor and then reuse the mock for the actual test with a new recording.
public class MockWithBeforeTest {
#Rule
public EasyMockRule rule = new EasyMockRule(this);
#Mock
private Dependency dependency;
private ClassUnderTest classUnderTest;
#Before
public void setUp() {
expect(this.dependency.getA()).andReturn(2);
replay(this.dependency);
this.classUnderTest = new ClassUnderTest(this.dependency);
reset(this.dependency);
}
#Test
public void testGetDerived() {
expect(this.dependency.getB()).andReturn(3);
replay(this.dependency);
assertEquals(6, this.classUnderTest.getDerived(1));
verify(this.dependency);
}
}

Powermock unexpectedly throwing InvalidUseOfMatchersException on final void method

I'm working to unit test my Java app..
My goal is to use Powermock to create a spy on an instance of the BOProcessor class. BOProcessor has a final void method; I will setup my spy to throw an exception when this method is called. I will also be mocking MyDao in this same test, but mocking this class is straightforward. The mocked MyDao will then be passed into an instance of MyDaoService named classUnderTest. I will then make assertions against classUnderTest.
Whenever I try to setup the above scenario, Powermock (or Mockito?) throws an InvalidUseOfMatchersException when I setup the doThrow on my spy. Strangely, this exception is only thrown when the doThrow expectation is followed by a call to classUnderTest. If I remove the later call the classUnderTest, the expectation works fine. Even weirder - classUnderTest doesn't even use the spy that is throwing the error!
This is the entirety of my test code outlined above. To highlight the problem, I've removed all code not directly related. (I've even removed the whole purpose of this test.)
package my.package;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import org.junit.Test;
public class WhatAmIDoingWrong {
#Test
public void whatAmIDoingWrong() {
MyDao mockedDao = mock(MyDao.class);
BOProcessor processor = new BOProcessor();
BOProcessor mockedProcessor = spy(processor);
MyDaoService classUnderTest = new MyDaoService(mockedDao);
doThrow(new Exception()).when(mockedProcessor).process(any(FakeBusinessObject.class));
classUnderTest.interactWithDao();
}
}
Here is the exception - thrown (ironically) from the doThrow line of my test code - which I'm trying to solve.
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
0 matchers expected, 1 recorded:
-> at my.package.WhatAmIDoingWrong.whatAmIDoingWrong(WhatAmIDoingWrong.java:21)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
at my.package.MyDaoService.interactWithDao(MyDaoService.java:33)
at my.package.WhatAmIDoingWrong.whatAmIDoingWrong(WhatAmIDoingWrong.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Here are the classes used by my test. To reiterate, the MyDaoService named classUnderTest doesn't even know about the spy of BOProcessor; it only works against the mock of MyDao. But the expectations on the BOProcessor spy only fail if the classUnderTest is called.
public class BOProcessor {
public final void process(FakeBusinessObject bar) {}
}
public class FakeBusinessObject {
}
import java.util.Collections;
import java.util.List;
public class MyDao {
public MyDao() {}
public List<String> getAllData(){
return Collections.emptyList();
}
}
public class MyDaoService {
private MyDao applicationDao;
public MyDaoService(MyDao applicationDao) {
this.applicationDao = applicationDao;
}
public synchronized void interactWithDao() {
applicationDao.getAllData();
}
}
I'm usin JUnit 4.12, Mockito 1.10.19, and Powermock 1.7.4. The project is running Spring 4.3.12RELEASE with spring-test included.
Why is Powermock throwing this exception? Am I not using the any Matcher correctly? Why on earth is this exception only thrown when a later call interacts with a different mock?
Thanks for the help!
It turns out that I was using Spies wrong. Something in the way org.mockito.stubbing.Stubber.when(T mock) is implemented means I cannot set expectations on the Spy the way I wanted to. But a Capture was actually a better fit for my use case anyway.
In the end, my test looked like this:
public class FixedNow{
#Test
public void fixedNow() {
MyDao mockedDao = mock(MyDao.class);
BOProcessor mockedProcessor = mock(BOProcessor.class);
FakeBusinessObject problematicBO = new FakeBusinessObject();
ArgumentCaptor<FakeBusinessObject> fakeBOCaptor = ArgumentCaptor.forClass(FakeBusinessObject.class);
MyDaoService classUnderTest = new MyDaoService(mockedDao, mockedProcessor);
doThrow(new Exception()).when(mockedProcessor).process(eq(problematicBO));
doNothing().when(mockedProcessor).process(fakeBOCaptor.capture());
classUnderTest.interactWithDao();
assertThings(BOCaptor.getValue());
}
}
Thanks for your thoughts!

Mocking a Spring Validator when unit testing Controller

While writing unit tests postmortem to code that another project created, I came across this issue of how to mock a validator that is bound to the controller with initBinder?
Normally I would just consider making sure my inputs are valid and be done with a few extra calls in the validator, but in this case the validator class is coupled with doing checks through a few data sources and it all becomes quite a mess to test. Coupling dates back to some old common libraries used and is outside the scope of my current work to fix all of them.
At first I tried to just mock out the external dependencies of the validator using PowerMock and mocking static methods, but eventually ran into a class that requires a data source when the class is created and didn't find a way around that one.
Then I tried to just use normal mockito tools to mock out the validator, but that didn't work either. Then tried to set the validator in the mockMvc call, but that doesn't register any more than a #Mock annotation for the validator. Finally ran into this question. But since there's no field validator on the controller itself, this fails too. So, how can I fix this to work?
Validator:
public class TerminationValidator implements Validator {
// JSR-303 Bean Validator utility which converts ConstraintViolations to Spring's BindingResult
private CustomValidatorBean validator = new CustomValidatorBean();
private Class<? extends Default> level;
public TerminationValidator(Class<? extends Default> level) {
this.level = level;
validator.afterPropertiesSet();
}
public boolean supports(Class<?> clazz) {
return Termination.class.equals(clazz);
}
#Override
public void validate(Object model, Errors errors) {
BindingResult result = (BindingResult) errors;
// Check domain object against JSR-303 validation constraints
validator.validate(result.getTarget(), result, this.level);
[...]
}
[...]
}
Controller:
public class TerminationController extends AbstractController {
#InitBinder("termination")
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
binder.setValidator(new TerminationValidator(Default.class));
binder.setAllowedFields(new String[] { "termId[**]", "terminationDate",
"accountSelection", "iban", "bic" });
}
[...]
}
Test class:
#RunWith(MockitoJUnitRunner.class)
public class StandaloneTerminationTests extends BaseControllerTest {
#Mock
private TerminationValidator terminationValidator = new TerminationValidator(Default.class);
#InjectMocks
private TerminationController controller;
private MockMvc mockMvc;
#Override
#Before
public void setUp() throws Exception {
initMocks(this);
mockMvc = standaloneSetup(controller)
.setCustomArgumentResolvers(new TestHandlerMethodArgumentResolver())
.setValidator(terminationValidator)
.build();
ReflectionTestUtils.setField(controller, "validator", terminationValidator);
when(terminationValidator.supports(any(Class.class))).thenReturn(true);
doNothing().when(terminationValidator).validate(any(), any(Errors.class));
}
[...]
}
Exception:
java.lang.IllegalArgumentException: Could not find field [validator] of type [null] on target [my.application.web.controller.TerminationController#560508be]
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:111)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:84)
at my.application.web.controller.termination.StandaloneTerminationTests.setUp(StandaloneTerminationTests.java:70)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
You should avoid creating business objects with new in a Spring application. You should always get them from the application context - it will ease mocking them in your test.
In your use case, you should simply create your validator as a bean (say defaultTerminationValidator) and inject it in your controller :
public class TerminationController extends AbstractController {
private TerminationValidator terminationValidator;
#Autowired
public setDefaultTerminationValidator(TerminationValidator validator) {
this.terminationValidator = validator;
}
#InitBinder("termination")
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
binder.setValidator(terminationValidator);
binder.setAllowedFields(new String[] { "termId[**]", "terminationDate",
"accountSelection", "iban", "bic" });
}
[...]
}
That way, you will be able to simply inject a mock in your test.
Well, the only way I know to deal with this situations, without changing your application code, using PowerMock.
It can instrument the JVM and creates mocks not only for static methods but also when you call new operator.
Take a look at this example:
https://code.google.com/p/powermock/wiki/MockConstructor
If you want to use Mockito, you have to use PowerMockito instead of PowerMock:
https://code.google.com/p/powermock/wiki/MockitoUsage13
Read the section How to mock construction of new objects
For instance:
My custom controller
public class MyController {
public String doSomeStuff(String parameter) {
getValidator().validate(parameter);
// Perform other operations
return "nextView";
}
public CoolValidator getValidator() {
//Bad design, it's better to inject the validator or a factory that provides it
return new CoolValidator();
}
}
My custom validator
public class CoolValidator {
public void validate(String input) throws InvalidParameterException {
//Do some validation. This code will be mocked by PowerMock!!
}
}
My custom test using PowerMockito
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.powermock.api.mockito.PowerMockito.*;
#RunWith(PowerMockRunner.class)
#PrepareForTest(MyController.class)
public class MyControllerTest {
#Test(expected=InvalidParameterException.class)
public void test() throws Exception {
whenNew(CoolValidator.class).withAnyArguments()
.thenThrow(new InvalidParameterException("error message"));
MyController controller = new MyController();
controller.doSomeStuff("test"); // this method does a "new CoolValidator()" inside
}
}
Maven dependencies
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
As you can see in my test, I'm mocking the validator behaviour, so it throws an exception when the controller invokes it.
However, the use of PowerMock usually denotes a bad design. It must be used typically when you have to test a legacy application.
If you can change the application, better change the code so it can be tested without instrumenting the JVM.

Powermock error mock object injected using spring when mocking static object

I am using PowerMock easy mock to mock static method of a class. I have two test cases written, which if I run independently run fine, but give me an error while running simultaneously.
CarTest:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ ServiceCaller.class })
public class CarTest {
ServiceCaller mockServiceCallerObjectToReturn;
public CarTest() {
PowerMock.mockStaticPartial(ServiceCaller.class, "getInstance");
mockServiceCallerObjectToReturn = PowerMock.createMock(ServiceCaller.class);
EasyMock.expect(ServiceCaller.getInstance()).andReturn(mockServiceCallerObjectToReturn);
}
#Test
public void test1() throws IOException {
PowerMock.reset(mockServiceCallerObjectToReturn);
PowerMock.reset(ServiceCaller.class);
EasyMock.expect(ServiceCaller.getInstance()).andReturn(mockServiceCallerObjectToReturn);
EasyMock.expect(mockServiceCallerObjectToReturn.checkValidity("testDriver")).andReturn(false);
PowerMock.replay(mockServiceCallerObjectToReturn);
PowerMock.replay(ServiceCaller.class);
Car car = CarFactory.getInstance().getCar();
boolean canDrive = car.drive("testDriver");
Assert.assertEquals(canDrive, false);
PowerMock.verify(mockServiceCallerObjectToReturn);
PowerMock.verify(ServiceCaller.class);
}
#Test
public void test2() throws IOException {
PowerMock.reset(mockServiceCallerObjectToReturn);
PowerMock.reset(ServiceCaller.class);
EasyMock.expect(ServiceCaller.getInstance()).andReturn(mockServiceCallerObjectToReturn);
EasyMock.expect(mockServiceCallerObjectToReturn.checkValidity("testDriver")).andReturn(false);
PowerMock.replay(mockServiceCallerObjectToReturn);
PowerMock.replay(ServiceCaller.class);
Car car = CarFactory.getInstance().getCar();
boolean canDrive = car.drive("testDriver");
Assert.assertEquals(canDrive, false);
PowerMock.verify(mockServiceCallerObjectToReturn);
PowerMock.verify(ServiceCaller.class);
}
}
CarFactory:
public class CarFactory {
private static final String CAR_SPRING_CONTEXT_XML = "/com/archit/mock/spring-config/CarSpringContext.xml";
protected static final ApplicationContext CONTEXT = new ClassPathXmlApplicationContext(new String[] { CAR_SPRING_CONTEXT_XML });
private static final CarFactory INSTANCE = new CarFactory();
public static CarFactory getInstance() {
return INSTANCE;
}
public Car getCar() {
return CONTEXT.getBean("car", Car.class);
}
}
Car:
package com.archit.mock;
public class Car {
private final ServiceCaller serviceCaller;
public Car(final ServiceCallerFactory serviceCallerFactory) {
this.serviceCaller = serviceCallerFactory.getServiceCaller();
}
public boolean drive(final String driver) {
return (serviceCaller.checkValidity(driver));
}
}
ServiceCaller:
package com.archit.mock;
public class ServiceCaller {
private static class ServiceCallerHolder {
private static ServiceCaller INSTANCE = new ServiceCaller();
}
public static ServiceCaller getInstance() {
return ServiceCallerHolder.INSTANCE;
}
public boolean checkValidity(final String x) {
// Do some call
throw new IllegalStateException("This should have been mocked");
}
}
ServiceCallerFactory:
package com.archit.mock;
public class ServiceCallerFactory {
public ServiceCaller getServiceCaller() {
return ServiceCaller.getInstance();
}
}
Spring Config:
<bean name="car" class="com.archit.mock.Car">
<constructor-arg>
<ref bean="serviceCallerFactory" />
</constructor-arg>
</bean>
<bean name="serviceCallerFactory" class="com.archit.mock.ServiceCallerFactory" />
Error:
java.lang.AssertionError:
Unexpected method call ServiceCaller.checkValidity("testDriver"):
ServiceCaller.checkValidity("testDriver"): expected: 1, actual: 2
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:44)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:85)
at org.easymock.internal.ClassProxyFactory$MockMethodInterceptor.intercept(ClassProxyFactory.java:94)
at com.archit.mock.ServiceCaller$$EnhancerByCGLIB$$9848ad9e.checkValidity(<generated>)
at com.archit.mock.Car.drive(Car.java:12)
at com.archit.mock.CarTest.test2(CarTest.java:60)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:66)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:312)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:86)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:94)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:296)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:284)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:84)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:209)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:148)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:102)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:42)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Other Observations:
Making the object scope as prototype in spring config works fine.
Both tests when run individually work fine.
Based on above 2, It seems to be a problem with re-setting the mocks.
I've found it easier with mocks to have them start as new objects each time the tests are run. You can achieve this by having all your mock objects at the top as private variables, using the Mockito.Mock annotation:
#Mock
private MockOneClass mockOne;
...
#Mock
private MockNClass mockN;
Then using the JUnit Before annotation, create a setup function of some sort that initializes all the mock objects:
#Before
public void setup() {
// initialize all the #Mock objects
MockitoAnnotations.initMocks(this);
}
This way, before each test runs you get a new Mock created, which you can then apply any amount of expects and functionality to, without having to worry about clearing out any old mocking done in a previous test. If you have any Mock that you know will provide some specific functionality (static singleton mock getInstance calls for example) can be called in this setup function to help keep the tests cleaner of Mock resetting.
I have some test that has 10+ tests all run in a row that uses this framework. Not only does it make it a lot easier to read, but it makes setting up tests from scratch really fast. Being able to copy old mock setups in Tests and remove/change certain pieces is so much easier then having to upkeep every mock object every test, which just doesn't extend well once you start getting more tests.

Categories