Java Unit Test mit Mockito

Java Unit Test mit Mockito

Zweck

In diesem Beitrag werde ich erklären, wie ein Unit-Test mit Mockito und JUnit durchgeführt wird. Das Beispiel wird von Spring Boot erstellt und verwendet die Konstruktorinjektion. Daher werden Anmerkungen wie "@ Mock" und "@ InjectMock" nicht angezeigt.

Was ist Mockito?

Mockito ist ein Open Source-Testframework für Java, das unter der MIT-Lizenz veröffentlicht wurde. (von Wikipedia) ~~ Als Mock Ito lesen. ~~ Als Moquito lesen. Auf diese Weise können Sie ganz einfach ein Scheinobjekt zum Testen erstellen.

Was macht dich glücklich mit einem Mock?

Es ist schwierig, einen Test für die folgenden Tests zu schreiben.

Sie können diese Probleme lösen, indem Sie die zu testende Klasse durch eine Klasse (Mock) ersetzen, die sich genauso verhält wie die abhängige Klasse. Die Vorteile sind wie folgt.

Material

Installation

plugins {
	id 'org.springframework.boot' version '2.3.2.RELEASE'
	id 'io.spring.dependency-management' version '1.0.9.RELEASE'
	id 'java'
}

group = 'com.mockito'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}

	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
}

test {
	useJUnitPlatform()
}

Mockito ist im Spring Boot Sterter-Test enthalten, sodass Sie nichts aus den Spring Boot-Standardeinstellungen hinzufügen müssen. Ich habe es hinzugefügt, weil ich nur Lombok verwenden wollte.

Testziel

Überblick

Nehmen Sie als Beispiel das Kassensystem, das den Gesamtpreis von Produkten berechnet. Der Gesamtbetrag wird berechnet, indem der für jeden Produkttyp festgelegte Steuersatz addiert wird. Der Steuersatz kann sich in Zukunft ändern und wird daher von der DB bezogen.

Produktobjektdefinition


package com.example.mockito;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

/**
 *Produkt Klassifikation
 * 
 * @author rcftdbeu
 *
 */
enum ItemType {
  Food, Other
}

/**
 *Produkt
 * 
 * @author rcftdbeu
 *
 */
@Getter
@Setter
@AllArgsConstructor
public class Item {
  /**Name*/
  String name;
  /**Preis*/
  int price;
  /**Art*/
  ItemType itemType;
}

Service zur Berechnung des Gesamtbetrags


package com.example.mockito;

import java.util.List;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class POS {

  private final TaxRateMaster taxRateMaster;

  /**
   *Berechnen Sie die Gesamtmenge der Waren
   *
   * @param itemList
   * @return
   */
  public int culculateTotal(List<Item> itemList) {
    //Gesamtgebühr
    int sum = 0;

    //Der Steuersatz wird nacheinander berechnet und addiert
    for (Item item : itemList) {
      sum += item.getPrice() * (1 + taxRateMaster.getTaxRate(item.getItemType()));
    }
    return sum;
  }
}

Klasse, die den zu verspottenden DB-Zugriff enthält


package com.example.mockito;

import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;

/**
 *Holen Sie sich den Steuersatz
 *
 * @author rcftdbeu
 *
 */
@RequiredArgsConstructor
@Component
public class TaxRateMaster {

  private final TaxRepository ripository;

  /**
   *Heutiges Datum
   *
   * @return
   */
  public Date getSysDate() {
    return new Date();
  }

  /**
   *Liegt es nach dem Antragsdatum für ermäßigten Steuersatz?
   *
   * @return
   */
  private boolean isAfterApplyDate() {
    String strDate = "2019/10/1 00:00:00";
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    try {
      Date applyDate = dateFormat.parse(strDate);
      return getSysDate().after(applyDate);
    } catch (Exception e) {
    }
    return false;
  }

  /**
   *Holen Sie sich den Steuersatz
   *
   * @param type
   * @return
   */
  public Double getTaxRate(ItemType type) {
    //Nach Anwendung des ermäßigten Steuersatzes
    if (isAfterApplyDate()) {
      if (type == ItemType.Food) {
        return ripository.getFoodTaxRate();
      } else {
        return ripository.getOtherTaxRate();
      }
    }

    //Vor Anwendung des ermäßigten Steuersatzes
    return ripository.getOldTaxRate();
  }
}

Repository


package com.example.mockito;

import org.springframework.stereotype.Repository;

/**
 *Prozess zum Abrufen des Steuersatzes von der DB<br>
 *Nicht implementiert, da DB nicht verwendet wird<br>
 *Existiert nur, um Kompilierungsfehler zu vermeiden
 * 
 * @author rcftdbeu
 *
 */
@Repository
public class TaxRepository {

  public Double getFoodTaxRate() {
    return null;
  }

  public Double getOtherTaxRate() {
    return null;
  }

  public Double getOldTaxRate() {
    return null;
  }
}

Unit Test

Endlich das Hauptthema.

In Mockito gibt es zwei Arten: mock und spy.

mock: Ein Mock, bei dem alle Methoden der Zielklasse durch "return null" ersetzt werden. Keine Initialisierung erforderlich. Spion: Muss mit demselben Objekt wie die Zielklasse initialisiert werden. Bei Bedarf können nur bestimmte Methoden geändert werden.

Mock

Wir werden einen Test mit Mock durchführen.

package com.example.mockito;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

public class MockTest {

  private static POS pos;
  private static TaxRateMaster master;
  private static TaxRepository repository;

  @Nested
Testen mit statischer Klasse Mock{

    //Vor dem Testen nur einmal ausführen
    @BeforeAll
    static void init() {
      master = mock(TaxRateMaster.class);
      pos = new POS(master);
    }

    //Vor jedem Test ausführen
    @BeforeEach
    void setup() {
      //Der Steuersatz für Lebensmittel beträgt 8%
      when(master.getTaxRate(ItemType.Food)).thenReturn(0.08);
      //Andere Steuersätze sind 10%
      when(master.getTaxRate(ItemType.Other)).thenReturn(0.10);
    }

    @Test
void Berechneter reduzierter Steuersatz für Lebensmittel_Berechnet mit 8%() {
      //Fügen Sie 100 Yen Reiskuchen hinzu
      List<Item> itemList = new ArrayList<Item>();
      Item onigiri = new Item("Reisbällchen", 100, ItemType.Food);
      itemList.add(onigiri);

      //Berechnung
      int sum = pos.culculateTotal(itemList);

      //Überprüfen Sie das Ergebnis
      assertThat(sum, is(108));
    }

    @Test
nichtig Sonstige Berechnung des ermäßigten Steuersatzes_Berechnet mit 10%() {
      //500 Yen Magazin hinzugefügt
      List<Item> itemList = new ArrayList<Item>();
      Item onigiri = new Item("Zeitschrift", 500, ItemType.Other);
      itemList.add(onigiri);

      //Berechnung
      int sum = pos.culculateTotal(itemList);

      //Überprüfen Sie das Ergebnis
      assertThat(sum, is(550));
    }
  }
}

init()

Der Mock wird initialisiert. Der TaxRateMaster, von dem der POS abhängt, wird durch einen Mock ersetzt. Die Struktur ist wie folgt. POS-Entity-TaxRateMaster-Mock

setup()

Es definiert das Verhalten des Mocks.

when () definiert, welche Methode aufgerufen wird und thenReturn () gibt was zurück.

Wenn Sie das Verhalten bei der Ausführung mit einem bestimmten Argument definieren möchten, geben Sie diesen Wert direkt an. Geben Sie anyString () oder any (ItemType.class) an, um das Verhalten bei Ausführung mit beliebigen Argumenten zu definieren.

Über die Notation

Es gibt zwei Methoden: Die eine besteht darin, mit "when (). ThenReturn ()" zu schreiben, und die andere darin, mit "doReturn (). When ()" zu schreiben. Wenn Sie das Setup-Beispiel neu schreiben, lautet es "doReturn (0.08) .when (master) .getTaxRate (ItemType.Food);".

Was besser ist, ist umstritten.

when (). thenReturn () Fraktion --⭕️ Einfach in der gleichen Reihenfolge wie if-Anweisungen zu lesen -❌ Kann mit Spion nicht verfügbar sein --❌ thenThrow ist nicht für Methoden verfügbar, die void zurückgeben → Wenn Sie richtig entwerfen, haben Sie keine Möglichkeit, doReturn zu verwenden.

doReturn (). When () Fraktion --⭕️ Sie können es jederzeit verwenden, ohne sich darum kümmern zu müssen -❌ Der in doReturn () beschriebene Wert wird zum Objekttyp und der Compiler kann den Fehler nicht erkennen. → Es ist einfacher, sich mit doReturn zu vereinen, als sich an subtile Unterschiede zu erinnern, und Sie können dies herausfinden, indem Sie die Typprüfung ausführen.

Persönlich gefällt mir when (). ThenReturn (), was leicht zu lesen ist, daher verwende ich diese Notation auch in diesem Beitrag.

Führen Sie den Test aus

Es gibt nichts Besonderes. Erstellen Sie einfach eine Item-Instanz zum Testen und führen Sie culculateTotal aus, das getestet werden soll.

Spy

@Nested
Testen mit der statischen Klasse Spy{

    //Vor dem Testen nur einmal ausführen
    @BeforeAll
    static void init() {
      repository = mock(TaxRepository.class);
      master = spy(new TaxRateMaster(repository));
      pos = new POS(master);
    }

    //Vor jedem Test ausführen
    @BeforeEach
    void setup() {
      //Der Steuersatz vor Anwendung des ermäßigten Steuersatzes beträgt 8%
      when(repository.getOldTaxRate()).thenReturn(0.08);
      //Der Lebensmittelsteuersatz beträgt 8%
      when(repository.getFoodTaxRate()).thenReturn(0.08);
      //Andere Steuersätze sind 10%
      when(repository.getOtherTaxRate()).thenReturn(0.10);
    }

    @Nested
Klasse Vor Anwendung des ermäßigten Steuersatzes{
      @BeforeEach
      void setup() throws ParseException {
        //Legen Sie fest, dass der Tag zurückgegeben wird, bevor der reduzierte Steuersatz angewendet wird, wenn das aktuelle Datum erfasst wird
        String strDate = "2019/9/30";
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
        Date applyDate = dateFormat.parse(strDate);
        when(master.getSysDate()).thenReturn(applyDate);
      }

      @Test
void Berechneter reduzierter Steuersatz für Lebensmittel_Berechnet mit 8%() {

        //Fügen Sie 100 Yen Reiskuchen hinzu
        List<Item> itemList = new ArrayList<Item>();
        Item onigiri = new Item("Reisbällchen", 100, ItemType.Food);
        itemList.add(onigiri);

        //Berechnung
        int sum = pos.culculateTotal(itemList);

        //Überprüfen Sie das Ergebnis
        assertThat(sum, is(108));
      }

      @Test
nichtig Sonstige Berechnung des ermäßigten Steuersatzes_Berechnet mit 8%() {
        //500 Yen Magazin hinzugefügt
        List<Item> itemList = new ArrayList<Item>();
        Item onigiri = new Item("Zeitschrift", 500, ItemType.Other);
        itemList.add(onigiri);

        //Berechnung
        int sum = pos.culculateTotal(itemList);

        //Überprüfen Sie das Ergebnis
        assertThat(sum, is(540));
      }
    }

    @Nested
Klasse Nach Anwendung des ermäßigten Steuersatzes{
      @BeforeEach
      void setup() throws ParseException {
        //Legen Sie fest, dass der Tag zurückgegeben wird, bevor der reduzierte Steuersatz angewendet wird, wenn das aktuelle Datum erfasst wird
        String strDate = "2019/10/1 00:00:01";
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date applyDate = dateFormat.parse(strDate);
        when(master.getSysDate()).thenReturn(applyDate);
      }

      @Test
void Berechneter reduzierter Steuersatz für Lebensmittel_Berechnet mit 8%() {
        //Fügen Sie 100 Yen Reiskuchen hinzu
        List<Item> itemList = new ArrayList<Item>();
        Item onigiri = new Item("Reisbällchen", 100, ItemType.Food);
        itemList.add(onigiri);

        //Berechnung
        int sum = pos.culculateTotal(itemList);

        //Überprüfen Sie das Ergebnis
        assertThat(sum, is(108));
      }

      @Test
nichtig Sonstige Berechnung des ermäßigten Steuersatzes_Berechnet mit 10%() {
        //500 Yen Magazin hinzugefügt
        List<Item> itemList = new ArrayList<Item>();
        Item onigiri = new Item("Zeitschrift", 500, ItemType.Other);
        itemList.add(onigiri);

        //Berechnung
        int sum = pos.culculateTotal(itemList);

        //Überprüfen Sie das Ergebnis
        assertThat(sum, is(550));
      }
    }
  }

init()

Spion initialisieren. Setzen Sie TaxRateMaster, von dem der POS abhängt, auf Spy und setzen Sie ihn auf Spy. Die Struktur ist wie folgt. POS-Entity-TaxRateMasters Spy-TaxRepository-Mock

setup()

Nur der Teil, der das aktuelle Datum in Spy of TaxRateMaster erfasst, wird durch das definierte Datum ersetzt.

Führen Sie den Test aus

Es ist dasselbe wie im Fall von Mock, und es gibt keine besondere Erklärung.

Verschiedene andere Verwendungen

Ändern Sie den beim ersten und beim zweiten Mal zurückgegebenen Wert

when(master.getTaxRate(ItemType.Food)).thenReturn(0.08)
  .thenReturn(0.10);

Durch Verbinden von "thenReturn" können beim ersten und zweiten Aufruf unterschiedliche Werte zurückgegeben werden. Das erste Mal kann für die Registrierung und das zweite Mal für die Aktualisierung verwendet werden.

Stellen Sie sicher, dass die Methode aufgerufen wurde

verify(master).getTaxRate(ItemType.Food);

Sie können überprüfen, ob die Mock-Methode aufgerufen wurde, indem Sie "verify" verwenden. Sie können auch überprüfen, ob das Argument zum Zeitpunkt der Ausführung der Methode mit dem angegebenen Argument ausgeführt wird.

Löschen Sie die Anzahl der Methodenaufrufe

@BeforeEach
void setup() {
  clearInvocations(master);
}

Es gibt nur eine Scheininstanz, und wenn sie aus mehreren Tests ausgeführt wird, wird die Anzahl der Aufrufe in anderen Tests bei der Überprüfung mit "verify" gezählt. Verwenden Sie daher vor der Ausführung des Tests "clearInvocations", um die Anzahl der Scheinaufrufe zu zählen. Sie müssen es zurücksetzen.

Stellen Sie sicher, dass die Methode mehrmals aufgerufen wurde

verify(master, times(2)).getTaxRate(ItemType.Food);

Sie können überprüfen, ob die Methode die angegebene Anzahl von Malen aufgerufen wurde, indem Sie dem Argument von "verify" "times" hinzufügen.

Stellen Sie sicher, dass die Methode nicht aufgerufen wurde

verify(master, never()).getTaxRate(ItemType.Food);

Sie können überprüfen, ob die Methode nicht aufgerufen wird, indem Sie dem Argument von "verify" "never" hinzufügen. Gleiches gilt für times (0).

Überprüfen Sie das Argument

Wenn das Argument der Methode, die Sie überprüfen möchten, ein primitiver Typ wie String oder Boolean ist, können Sie es nach der Überprüfung mit dem Argument der Methode überprüfen, jedoch für andere Objekte. Sie können das Argument mit ArgumentCaptor abrufen.

@Test
Holen Sie sich nichtiges Argument() {
  //Generieren Sie ArgumentCaptor, um Argumente abzurufen
  ArgumentCaptor<ItemType> argCaptor = ArgumentCaptor.forClass(ItemType.class);

  //Fügen Sie 100 Yen Reiskuchen hinzu
  List<Item> itemList = new ArrayList<Item>();
  itemList.add(new Item("Reisbällchen", 100, ItemType.Food));
  
  //Berechnungsausführung
  pos.culculateTotal(itemList);
  
  //Argumente abrufen
  verify(master).getTaxRate(argCaptor.capture());
  ItemType executedItemType = argCaptor.getValue();
  
  //Überprüfen Sie die Laufzeitargumente
  assertThat(executedItemType, is(ItemType.Food));
}

Überprüfen Sie das Verhalten, wenn eine Ausnahme auftritt

@Test
void Ausnahme aufgetreten() {
  when(master.getTaxRate(ItemType.Food)).thenThrow(new RuntimeException("DB-Verbindungsfehler"));

  //Fügen Sie 100 Yen Reiskuchen hinzu
  List<Item> itemList = new ArrayList<Item>();
  itemList.add(new Item("Reisbällchen", 100, ItemType.Food));

  //Berechnung
  assertThrows(RuntimeException.class, () -> pos.culculateTotal(itemList));

  //Deaktivieren Sie dann Einstellungen werfen
  reset(master);
}

Sie können eine Ausnahme auslösen, indem Sie "thenThrow" anstelle von "thenReturn" setzen. Ausnahmen können nur für Ausnahmen festgelegt werden, die von der entsprechenden Methode ausgelöst werden. Da bei dieser Methode keine Ausnahme festgelegt ist, wird "RuntimeException" ausgelöst. Außerdem möchte ich in anderen Tests keine Ausnahme auslösen, daher habe ich sie mit "Zurücksetzen" gelöscht.

Andere testbezogene

Ich möchte die statische Methode verspotten

Standardmäßig kann es nicht verspottet werden. Die statische Methode ist nicht mit mock kompatibel. Daher sollten Sie sie nach Möglichkeit nicht statisch machen. Es scheint, dass es mit Power Mock in Mock konvertiert werden kann, aber ich wusste nicht, wie ich es mit JUnit5 bedienen soll.

Ich möchte eine private Methode testen

Dies kann auch nicht verspottet werden. Private Methoden sollten mit öffentlichen Methoden getestet werden können. Implementieren Sie daher Tests für öffentliche Methoden. Wenn Sie wirklich wollen, müssen Sie Power Mock verwenden oder schwarze Magie wie Reflexion ausprobieren.

Recommended Posts

Java Unit Test mit Mockito
So führen Sie einen Java-Komponententest durch (JUnit & Mockito & PowerMock)
Installieren Sie Java mit Homebrew
Wechseln Sie die Plätze mit Java
Installieren Sie Java mit Ansible
[Ralis] Über Unit Test
Bequemer Download mit JAVA
Java Unit Test Library-Arterien-Probe
Schalten Sie Java mit direnv
Java-Download mit Ansible
Lass uns mit Java kratzen! !!
Machen Sie einen Unit-Test mit Junit.
Erstellen Sie Java mit Wercker
Endian-Konvertierung mit JAVA
Generieren Sie Dummy-Daten für verschiedene Tests mit Faker (Java)
(Java) Einfache BDD mit Spektrum?
Verwenden Sie Lambda-Ebenen mit Java
Erstellen Sie mit Gradle ein Java-Multiprojekt
Erste Schritte mit Java Collection
Java-Konfiguration mit Spring MVC
Grundlegende Authentifizierung mit Java 11 HttpClient
Experimentieren wir mit der Java-Inline-Erweiterung
Führen Sie Batch mit Docker-Compose mit Java-Batch aus
[Vorlage] MySQL-Verbindung mit Java
Schreiben Sie Java Try-Catch mit Optional neu
Installieren Sie Java 7 mit Homebrew (Fass)
[Java] JSON-Kommunikation mit Jackson
Java zum Spielen mit Function
Aktivieren Sie Java EE mit NetBeans 9
[Java] JavaConfig mit statischer innerer Klasse
Lassen Sie uns Excel mit Java betreiben! !!
Java-Versionsverwaltung mit SDKMAN
RSA-Verschlüsselung / Entschlüsselung mit Java 8
Paging PDF mit Java + PDFBox.jar
Verschiedene Methodentests mit MockRestServiceServer
[Java] Inhaltserfassung mit HttpCliient
Java-Versionsverwaltung mit jenv
Fehlerbehebung mit Java Flight Recorder
Stellen Sie mit Java eine Verbindung zur Datenbank her
Stellen Sie mit Java eine Verbindung zu MySQL 8 her
Fehler beim Spielen mit Java
Erste Schritte mit Java Basics
Saisonale Anzeige mit Java-Schalter
Verwenden Sie SpatiaLite mit Java / JDBC
Vergleichen Sie Java 8 Optional mit Swift
Führen Sie Java VM mit Web Assembly aus
Verwenden Sie Spring Test + Mockito + JUnit 4 für Spring Boot + Spring Retry Unit Test
Bildschirmübergang mit Swing, Java
[Java 8] Doppelte Löschung (& doppelte Überprüfung) mit Stream
Java-Lambda-Ausdruck, der mit Comparator gelernt wurde
Überprüfen Sie Argumente mit ArgumentCaptor mit mockito
Erstellen eines Java-Projekts mit Gradle
Installieren Sie Java mit Ubuntu 16.04 Docker
Java mit Ramen lernen [Teil 1]
Morphologische Analyse in Java mit Kuromoji
Verwenden Sie Java mit MSYS und Cygwin
Verteilte Ablaufverfolgung mit OpenCensus und Java
Java Unit Test Library-Arterie-ArValidator Validiert Objekte