61

I am trying to test a service class, which internally makes use of a Spring AMQP connection object. This connection object is injected by Spring. However, I don't want my unit test to actually communicate with the AMQP broker, so I am using Mockito inject a mock of the connection object.

/** 
 * The real service class being tested.  Has an injected dependency. 
 */ 
public class UserService {

   @Autowired
   private AmqpTemplate amqpTemplate;

   public final String doSomething(final String inputString) {
      final String requestId = UUID.randomUUID().toString();
      final Message message = ...;
      amqpTemplate.send(requestId, message);
      return requestId;
   }
}

/** 
 * Unit test 
 */
public class UserServiceTest {

   /** This is the class whose real code I want to test */
   @InjectMocks
   private UserService userService;

   /** This is a dependency of the real class, that I wish to override with a mock */
   @Mock
   private AmqpTemplate amqpTemplateMock;

   @Before
   public void initMocks() {
      MockitoAnnotations.initMocks(this);
   }

   @Test
   public void testDoSomething() {
      doNothing().when(amqpTemplateMock).send(anyString(), any(Message.class));

      // Call the real service class method, which internally will make 
      // use of the mock (I've verified that this works right).
      userService.doSomething(...);

      // Okay, now I need to verify that UUID string returned by 
      // "userService.doSomething(...) matches the argument that method 
      // internally passed to "amqpTemplateMock.send(...)".  Up here 
      // at the unit test level, how can I capture the arguments passed 
      // to that inject mock for comparison?
      //
      // Since the value being compared is a UUID string created 
      // internally within "userService", I cannot just verify against 
      // a fixed expected value.  The UUID will by definition always be
      // unique.
   }
}

The comments in this code sample hopefully lay out the question clearly. When Mockito injects a mock dependency into a real class, and unit tests on the real class cause it to make calls to the mock, how can you later retrieve the exact arguments that were passed to the injected mock?

Cemal Unal
  • 514
  • 3
  • 6
  • 15
Steve Perkins
  • 11,520
  • 19
  • 63
  • 95

2 Answers2

112

Use one, or more, ArgumentCaptors.

It is unclear what your types are here, but anyway. Let's suppose you have a mock which has a method doSomething() taking a Foo as an argument, then you do this:

final ArgumentCaptor<Foo> captor = ArgumentCaptor.forClass(Foo.class);

verify(mock).doSomething(captor.capture());

final Foo argument = captor.getValue();

// Test the argument

Also, it looks like your method returns void and you don't want it to do anything. Just write this:

doNothing().when(theMock).doSomething(any());
fge
  • 119,121
  • 33
  • 254
  • 329
  • 1
    Thanks for the tip on `doAnswer()` vs. `doNothing()`, I have cleaned up the above code sample accordingly. However, I'm not sure if I'm missing something, or if your answer is still one level removed from my problem. When I start by calling `verify()` on my mock object BEFORE I've called the real class which uses the mock internally, then I get the exception message, "Actually, there were zero interactions with this mock". Execution fails on the `verify()` line... and never even makes it to the real code that I'm trying to test. – Steve Perkins Mar 20 '15 at 15:32
  • 1
    In your example, are you trying to pass an argument to your mock object directly from the unit test? I am not... I'm trying to capture an argument that's passed by an intermediary layer between the unit test and the mock object. The mock is being injected into this intermediary. – Steve Perkins Mar 20 '15 at 15:33
  • 11
    Ahh! The issue that was unclear from your snippet is that the `verify()` call on the mock, as well as the `getValue()` call on the captor, both come AFTER the method invocation on the real object actually under test. I was mistakenly calling `verify()` prior to the real object method invocation. – Steve Perkins Mar 20 '15 at 15:54
  • 1
    @StevePerkins oops, sorry, I wasn't there, but I see that you have figured it out ;) – fge Mar 20 '15 at 16:51
  • 1
    There is a shortcut to create captor in class level, as well: @Captor private ArgumentCaptor fooCaptor; – ozeray May 18 '18 at 07:35
12

You can hook doAnswer() to the stub of the send() method on amqpTemplateMock and then capture the invocation arguments of AmqpTemplate.send().

Make the first line of your testDoSomething() be this

    Mockito.doAnswer(new Answer<Void>() {
          @Override
          public Void answer(final InvocationOnMock invocation) {
            final Object[] args = invocation.getArguments();
            System.out.println("UUID=" + args[0]);  // do your assertions here
            return null;
          }
    }).when(amqpTemplateMock).send(Matchers.anyString(), Matchers.anyObject());

putting it all together, the test becomes

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class UserServiceTest {

  /** This is the class whose real code I want to test */
  @InjectMocks
  private UserService userService;

  /** This is a dependency of the real class, that I wish to override with a mock */
  @Mock
  private AmqpTemplate amqpTemplateMock;

  @Before
  public void initMocks() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void testDoSomething() throws Exception {
    Mockito.doAnswer(new Answer<Void>() {
      @Override
      public Void answer(final InvocationOnMock invocation) {
        final Object[] args = invocation.getArguments();
        System.out.println("UUID=" + args[0]);  // do your assertions here
        return null;
      }
    }).when(amqpTemplateMock).send(Matchers.anyString(), Matchers.anyObject());
    userService.doSomething(Long.toString(System.currentTimeMillis()));
  }
}

This gives output

UUID=8e276a73-12fa-4a7e-a7cc-488d1ce0291f

I found this by reading this post, How to make mock to void methods with mockito

Community
  • 1
  • 1
Kirby
  • 15,127
  • 10
  • 89
  • 104
  • Nice answer. Additionally, recent versions of Mockito (I am using 2.24.5) include interfaces `Answer1` to `Answer5` to provide easier use for invocations with up to five arguments, for example to implement `Answer1` you have to provide a `public String answer(final Integer argument0) throws Throwable` method. – SJuan76 Jul 28 '21 at 11:14