5

I’m using Spring 3.2.11.RELEASe, JUnit 4.12, and Mockito 1.10.18. In my JUnit test, how do I create a spy (not a mock, a spy) of an @Autowired spring service? Here’s how the service is declared …

@Service("orderService")
public class OrderServiceImpl implements OrderService, InitializingBean 
{

and here is how my JUnit test is set up …

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class ProcessPDWorkerTest extends AbstractWorkerTest
{
    …    
    @Autowired
    protected OrderService m_orderSvc;

with

      final OrderService orderSvcSpy = Mockito.spy(getTargetObject(m_orderSvc));
    …
      ReflectionTestUtils.setField(workerObj, "m_orderSvc", orderSvcSpy);

where I have …

protected static <T> T getTargetObject(Object proxy)
{
    if ((AopUtils.isJdkDynamicProxy(proxy)))
    {
        try
        {
            return (T) getTargetObject(((Advised) proxy).getTargetSource().getTarget());
        }
        catch (Exception e)
        {
            throw new RuntimeException("Failed to unproxy target.", e);
        }
    }
    return (T) proxy;
}

but I get the following exception on the line “Mockito.spy(getTargetObject(m_orderSvc))”:

java.lang.ClassCastException: org.mainco.subco.myproject.service.OrderServiceImpl cannot be cast to java.lang.Class
    at org.mainco.subco.test.worker.AbstractWorkerTest.createMockOrders(AbstractWorkerTest.java:146)
    at org.mainco.subco.orders.ProcessPDWorkerTest.mockTrainingAssignmentAndOrder(ProcessPDWorkerTest.java:1117)
    at org.mainco.subco.orders.ProcessPDWorkerTest.testCreateTrainingSessionWTrainer(ProcessPDWorkerTest.java:297)
    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:497)
    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.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    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:231)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
    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:174)
    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: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)
Dave
  • 15,639
  • 133
  • 442
  • 830
  • Why you want to spy a dependency ? Spy is really meaningful only when testing logic inside the class itself. If you need the real OrderService just use Autowired otherwise you can mock the class (without using Spring DI ) and eventually stub one method as callRealMethod. – Gualtiero Testa Jul 26 '15 at 18:11
  • 1
    I want some of the real methods of OrderService but want to mock others. That's waht a spy is for or is there another concept in this version of Mockito that is equivalent to spy? – Dave Jul 27 '15 at 13:42
  • I apologize but I can't make out what line of code is throwing the exception. Can you point that out? – Jose Martinez Jul 28 '15 at 14:27
  • Are you sure `getTargetObject()` is returning an instance of `OrderService`? – Fred Porciúncula Jul 28 '15 at 19:02
  • #getTargetObject supposed to use j7 type inference, but because there's no ref, the returned type is Object. making this: final OrderService targetObject = getTargetObject(m_orderSvc) would solve this – hahn Jul 29 '15 at 07:58
  • you can take a look on the steps that are taken to infer the type here https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html – hahn Jul 29 '15 at 08:08
  • or even better solution, would be to change the signature of a method from "getTargetObject(Object proxy)" to "getTargetObject(T proxy)". – hahn Jul 29 '15 at 08:16

2 Answers2

6

Alternatively, you can try to change only this line:

 final OrderService orderSvcSpy = Mockito.spy((OrderService)getTargetObject(m_orderSvc));
 …

This should work. Mockito.spy() is an overloaded static method and compiler choses Mockito.spy(Class) at compile time.

Alex Borysov
  • 281
  • 1
  • 4
2

Works for me:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class ProcessPDWorkerTest extends AbstractWorkerTest {

    …    
    @Spy
    @Autowired
    protected OrderService m_orderSvc;

    @Before
    public void initMocks() {
        MockitoAnnotations.initMocks(this);
        ReflectionTestUtils.setField(workerObj, "m_orderSvc", m_orderSvc);
    }
Alex Borysov
  • 281
  • 1
  • 4
  • the question post shows that there is #getTargetObject that is retrieving the original object in case of proxy object – hahn Jul 29 '15 at 06:50
  • I see, but I'm not sure if #getTargetObject call is required in test logic because in most cases tests don't really care is it target or proxy object. – Alex Borysov Jul 29 '15 at 07:25