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!
Related
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.
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);
}
}
I am new to Junit and come across this problem recently. I am not able code write test cases wherever I used CompletableFuture in my code. Like below Java file
Updated
AuditService.java
#Autowired
Executor existingThreadPool;
#Override
public void auditData(List<ErrorDetails> alertList) {
CompletableFuture.runAsync(() -> {
if (alertList.isEmpty())
//privateMethodCall1
else
//privateMethodCall2
}, existingThreadPool);
}
I followed this link and tried below solution still getting NPE for CompletableFuture Like below error.
AuditServiceTest.java
#InjectMock
AuditService auditService;
#Mock
private CompletableFuture<Void> completableFuture = null;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
completableFuture = CompletableFuture.runAsync(new Runnable() {
#Override
public void run() {}
},Executors.newSingleThreadExecutor());
}
#Test
public void shouldAuditData() {
List<ErrorDetails> alertList = new ArrayList();
auditService.auditData(alertList);
}
ERROR
java.lang.NullPointerException
at java.util.concurrent.CompletableFuture.screenExecutor(CompletableFuture.java:415)
at java.util.concurrent.CompletableFuture.runAsync(CompletableFuture.java:1858)
at com.service.impl.AuditService.auditData(AuditService.java:15)
at com.service.impl.AuditServiceTest.shouldAuditData(AuditServiceTest.java:249)
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.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:86)
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:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
You need to test your logic and you don't need to mock the static method CompletableFuture.runAsync(...). So your test should look like normal test with exception that you need to wait some time to be sure that asynchronous code is executed, because it is not executed in the same thread. So for the moment I will give you example that you can use with Thread.sleep() which is not good convention, in additional question you can ask how to avoid usages of Thread.sleep().
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
#RunWith(MockitoJUnitRunner.class)
public class AuditServiceTest {
#Mock
Service serviceMock;
#Test
public void shouldAuditData() {
AuditService auditService = new AuditService(serviceMock);
List<Object> alertList = new ArrayList();
auditService.auditData(alertList);
// you can wait with Thread.sleep()
// because the execution is asynchronous
Mockito.verify(serviceMock).method1();
Mockito.verify(serviceMock, Mockito.never()).method2();
}
}
class AuditService {
Executor existingThreadPool = Executors.newSingleThreadExecutor();
Service service;
public AuditService(Service service) {
this.service = service;
}
public void auditData(List<Object> alertList) {
CompletableFuture.runAsync(() -> {
if (alertList.isEmpty()) {
service.method1();
} else {
service.method2();
}
}, existingThreadPool);
}
}
class Service {
public void method1(){};
public void method2(){};
}
in AuditService class , Executor is autowired. that is perfect setup for unit tests. what you have to do is , come up with separate configuration for test and Executor implementation should be a inline executor (you can provide your own implementation which calls runnable.run in the same calling thread).
To do this you can use some implementations provided spring-test.
ex: AbstractJUnit4SpringContextTests
if you dont like to go with spring-test support, now you have injected mock Executor to AuditService. so you can mock the execute method with providing custom stub.Answer and execute the runnable.run.
Mockito.doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
((Runnable)args[0]).run();
return null; // void method, so return null
}
}).when(executor).execute(Mockito.any(Runnable.class));
When I try to run the following test
public class FavoriteServiceTest extends AbstractCoreTest {
#Autowired
private FavoriteRepository favoriteRepository;
#Autowired
private RevisionService revisionService;
#Autowired
private FavoriteService favoriteService;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(revisionService.getGlobalRevisionNumber()).thenReturn(1L);
}
#Test
public void loadFavorites() throws Exception {
when(favoriteRepository.findFavoritesByUserId("123")).thenReturn(Collections.emptyList());
List<Favorite> favorites = favoriteService.loadFavorites(123L);
assertThat(favorites.size(), is(0));
}
I get the following exception, but im pretty sure the mock is correct initialized
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods. Those methods cannot be stubbed/verified. Mocking methods
declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.
at FavoriteServiceTest.setUp(FavoriteServiceTest.java:44) 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.RunBefores.evaluate(RunBefores.java:24)
Just replace #Autowired with #Mock -.-
#Mock
private DocumentService documentService;
Your favoriteRepository should be a mock object, benfinit from spring boot, you can using #MockBean here.
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.