Wie kann Mockito Argumente erfassen, die an die Methoden eines eingefügten Mock-Objekts übergeben werden?

Lesezeit: 5 Minuten

Benutzeravatar von Steve Perkins
Steve Perkins

Ich versuche, eine Dienstklasse zu testen, die intern ein Spring-AMQP-Verbindungsobjekt verwendet. Dieses Verbindungsobjekt wird von Spring injiziert. Ich möchte jedoch nicht, dass mein Komponententest tatsächlich mit dem AMQP-Broker kommuniziert, also verwende ich Mockito, um einen Schein des Verbindungsobjekts einzufügen.

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

Die Kommentare in diesem Codebeispiel stellen die Frage hoffentlich klar dar. Wenn Mockito eine Scheinabhängigkeit in eine reale Klasse einfügt und Komponententests für die reale Klasse dazu führen, dass sie Aufrufe an die Scheinstruktur tätigt, wie können Sie dann später die genauen Argumente abrufen, die an die eingefügte Scheinklasse übergeben wurden?

Verwenden Sie eine oder mehrere ArgumentCaptorS.

Es ist unklar, was Ihre Typen hier sind, aber trotzdem. Nehmen wir an, Sie haben einen Mock, der eine Methode hat doSomething() ein Nehmen Foo als Argument, dann machst du das:

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

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

final Foo argument = captor.getValue();

// Test the argument

Außerdem sieht es so aus, als ob Ihre Methode void zurückgibt und Sie nicht möchten, dass sie etwas tut. Schreib einfach das:

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

  • Danke für den Tipp bzgl doAnswer() vs. doNothing(), habe ich das obige Codebeispiel entsprechend bereinigt. Ich bin mir jedoch nicht sicher, ob ich etwas übersehe oder ob Ihre Antwort immer noch eine Ebene von meinem Problem entfernt ist. Wenn ich anfange anzurufen verify() auf meinem Scheinobjekt BEVOR ich die reale Klasse aufgerufen habe, die den Schein intern verwendet, dann erhalte ich die Ausnahmemeldung “Eigentlich gab es keine Interaktionen mit diesem Schein”. Die Ausführung schlägt auf der fehl verify() Zeile … und schafft es nicht einmal zu dem echten Code, den ich zu testen versuche.

    – Steve Perkins

    20. März 2015 um 15:32 Uhr


  • Versuchen Sie in Ihrem Beispiel, ein Argument direkt aus dem Komponententest an Ihr Scheinobjekt zu übergeben? Ich bin nicht … Ich versuche, ein Argument zu erfassen, das von einer Zwischenschicht zwischen dem Komponententest und dem Scheinobjekt übergeben wird. Der Schein wird in diesen Vermittler injiziert.

    – Steve Perkins

    20. März 2015 um 15:33 Uhr

  • Aha! Das Problem, das aus Ihrem Snippet unklar war, ist, dass die verify() Anruf auf der Mock, sowie die getValue() Aufruf des Captors, beide kommen NACH dem Methodenaufruf auf dem realen Objekt, das tatsächlich getestet wird. Ich habe fälschlicherweise angerufen verify() vor dem Methodenaufruf des realen Objekts.

    – Steve Perkins

    20. März 2015 um 15:54 Uhr

  • @StevePerkins oops, sorry, ich war nicht dabei, aber ich sehe, dass du es herausgefunden hast 😉

    – fg

    20. März 2015 um 16:51 Uhr

  • Es gibt auch eine Abkürzung, um Captor auf Klassenebene zu erstellen: @Captor private ArgumentCaptor fooCaptor;

    – Ozeray

    18. Mai 2018 um 7:35 Uhr


Kirbys Benutzeravatar
Kirby

Sie können haken doAnswer() zum Stummel der send() Methode an amqpTemplateMock und erfassen Sie dann die Aufrufargumente von AmqpTemplate.send().

Machen Sie die erste Zeile Ihrer testDoSomething() sei dies

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

Alles zusammengenommen wird der Test

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

Dies ergibt eine Ausgabe

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

Ich habe dies gefunden, indem ich diesen Beitrag gelesen habe, Wie man Mock-to-Void-Methoden mit Mockito erstellt

  • Gute Antwort. Darüber hinaus enthalten neuere Versionen von Mockito (ich verwende 2.24.5) Schnittstellen Answer1 Zu Answer5 Aufrufe mit bis zu fünf Argumenten einfacher nutzbar zu machen, zum Beispiel zu implementieren Answer1<String, Integer> Sie müssen eine angeben public String answer(final Integer argument0) throws Throwable Methode.

    – SJuan76

    28. Juli 2021 um 11:14 Uhr

1449760cookie-checkWie kann Mockito Argumente erfassen, die an die Methoden eines eingefügten Mock-Objekts übergeben werden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy