web-dev-qa-db-fra.com

Comment Mockito peut-il capturer les arguments passés aux méthodes d'un objet fictif injecté?

J'essaie de tester une classe de service qui utilise en interne un objet de connexion Spring AMQP. Cet objet de connexion est injecté par Spring. Cependant, je ne veux pas que mon test unitaire communique réellement avec le courtier AMQP, alors j'utilise Mockito pour injecter une maquette de l'objet de connexion.

/** 
 * 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.
   }
}

J'espère que les commentaires de cet exemple de code exposent clairement la question. Lorsque Mockito injecte une dépendance fictive dans une classe réelle et que des tests unitaires sur la classe réelle lui permettent de passer des appels à la maquette, comment récupérer plus tard les arguments exacts qui ont été transmis à la maquette injectée?

34
Steve Perkins

Utilisez un ou plusieurs ArgumentCaptor s.

On ne sait pas ce que sont vos types ici, mais de toute façon. Supposons que vous avez une simulation qui a une méthode doSomething() prenant un argument Foo, puis procédez comme suit:

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

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

final Foo argument = captor.getValue();

// Test the argument

En outre, il semble que votre méthode retourne à zéro et que vous ne vouliez rien faire. Il suffit d'écrire ceci:

doNothing().when(theMock).doSomething(any());
70
fge

Vous pouvez accrocher doAnswer() au moignon de la méthode send() sur amqpTemplateMock, puis capturer les arguments d'appel de AmqpTemplate.send().

Faites que la première ligne de votre testDoSomething() soit la suivante

    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());

en mettant le tout ensemble, le test devient

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

Cela donne une sortie

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

J'ai trouvé cela en lisant ce post, Comment se moquer pour annuler les méthodes avec mockito

8
Kirby