J'essaie de simuler le comportement d'une classe en utilisant Mockito . Cela a fonctionné avec Mockito 1.x. Migration vers JUnit 5 et Mockito 2, cela ne semble plus fonctionner.
@ExtendWith(MockitoExtension.class)
public class MockitoExample {
static abstract class TestClass {
public abstract int booleanMethod(boolean arg);
}
@Mock
TestClass testClass;
@BeforeEach
public void beforeEach() {
when(testClass.booleanMethod(eq(true))).thenReturn(1);
when(testClass.booleanMethod(eq(false))).thenReturn(2);
}
@Test
public void test() {
assertEquals(1,testClass.booleanMethod(true));
assertEquals(2,testClass.booleanMethod(false));
}
}
On s'attend à ce que TestClass simulé montre le comportement tel que testé dans la méthode de test.
L'erreur que je reçois est:
org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:
- this invocation of 'booleanMethod' method:
testClass.booleanMethod(false);
-> at org.oneandone.ejbcdiunit.mockito_example.MockitoExample.beforeEach(MockitoExample.Java:30)
- has following stubbing(s) with different arguments:
1. testClass.booleanMethod(false);
-> at org.oneandone.ejbcdiunit.mockito_example.MockitoExample.beforeEach(MockitoExample.Java:29)
Typically, stubbing argument mismatch indicates user mistake when writing tests.
Mockito fails early so that you can debug potential problem easily.
However, there are legit scenarios when this exception generates false negative signal:
- stubbing the same method multiple times using 'given().will()' or 'when().then()' API
Please use 'will().given()' or 'doReturn().when()' API for stubbing.
- stubbed method is intentionally invoked with different arguments by code under test
Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
For more information see javadoc for PotentialStubbingProblem class.
Dans les deux cas, l'argument false
semble être apparié, bien que j'aie clairement apparié avec true
.
Est-ce un bug dans Mockito 2.17 ou un malentendu? Comment dois-je/puis-je utiliser Mockito 2.x pour simuler des appels avec différents arguments booléens?
Le exemple peut également être trouvé sur github. Mais surefire commencera le test en utilisant uniquement
mvn test -Dtest=MockitoExample
L'exécution du test à l'aide de Mockito 2.21 conduit aux mêmes résultats.
Avec des stubs stricts (comportement par défaut de Mockito), appeler plusieurs when
s sur la même méthode réinitialisera cette simulation. La solution consiste à appeler when
une fois et à avoir la logique dans un Answer
:
@BeforeEach
public void beforeEach() {
when(testClass.booleanMethod(anyBoolean())).thenAnswer(invocationOnMock -> {
if ((boolean) invocationOnMock.getArguments()[0]) {
return 1;
}
return 2;
});
}
Alternativement, vous pouvez utiliser des moqueries indulgentes, mais ce n’est pas toujours une bonne idée, car elles vous permettent d’aggraver les choses et vous permettent de faire plus facilement des erreurs lors de votre test, ce qui peut entraîner des bogues inaperçus dans le code "de production":
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class MockitoExample {
Depuis Mockito 2.20, il est également possible d’ajouter localement ()
@ExtendWith(MockitoExtension.class)
public class MockitoExample {
static abstract class TestClass {
public abstract int booleanMethod(boolean arg);
}
@Mock
TestClass testClass;
@BeforeEach
public void beforeEach() {
lenient().when(testClass.booleanMethod(eq(true))).thenReturn(1);
lenient().when(testClass.booleanMethod(eq(false))).thenReturn(2);
}
@Test
public void test() {
assertEquals(1,testClass.booleanMethod(true));
assertEquals(2,testClass.booleanMethod(false));
}
}
Les Mockito 1 et 2 n'ont pas le même niveau de "sévérité".
Outre l'utilisation de Mockito 2 avec JUnit 4 ou 5, le niveau par défaut sera toujours différent.
Pour résumer :
3 niveaux de rigueur:
LENIENT
: rigueur minimaleWARN
: avertissements supplémentaires émis sur la consoleSTRICT_STUBS
: garantit des tests clairs en générant une exception en cas de mauvaise utilisation, mais peut également produire des faux positifs.Niveau effectif par défaut en fonction des API utilisées:
LENIENT
WARN
MockitoExtension.class
): STRICT_STUBS
STRICT_STUBS
.Plus de détails
La documentation de Mockito est très claire à ce sujet:
Le Strictness
javadoc déclare:
Configure la "rigueur" de Mockito lors d'une session moqueuse.A session correspond généralement à une seule invocation de méthode de test. Rigueur conduit des tests plus propres et une meilleure productivité.La manière la plus simple de Exploitation améliorée Strictness utilise le support JUnit de Mockito (MockitoRule ou MockitoJUnitRunner) .Si vous ne pouvez pas utiliser le support JUnit MockitoSession est la voie à suivre.
Comment le niveau de rigueur influence-t-il le comportement du test (session moqueuse )?
1 .
Strictness.LENIENT
- pas de comportement ajouté. Paramètre par défaut de Mockito 1.x.Recommandé uniquement si vous ne pouvez pas utiliser STRICT_STUBS ni WARN.2 .
Strictness.WARN
- aide à garder les tests propres et à améliorer la capacité de débogage.Reporte les avertissements de la console concernant les talons et talons inutilisés Argument mismatch (voir org.mockito.quality.MockitoHint) .La valeur par défaut comportement de Mockito 2.x lorsque JUnitRule ou MockitoJUnitRunner sont utilisés . Recommandé si vous ne pouvez pas utiliser STRICT_STUBS.3 .
Strictness.STRICT_STUBS
- garantit des tests clairs, réduit la duplication du code de test, améliore la capacité de débogage. Meilleure combinaison de flexibilité et la productivité. Fortement recommandé.Planifié par défaut pour Mockito v3.Voir STRICT_STUBS pour plus de détails.
Mais quelle que soit l'exception levée associée au message
"a stubbing suivant avec des arguments différents"
semble être un contrôle excessivement strict. Le message d'exception prouve que d'une certaine manière:
Cependant, il existe des scénarios légitimes lorsque cette exception génère false signal négatif:
...
- la méthode stubbed est invoquée intentionnellement avec différents arguments par le code sous test
Donc, l'interdire par défaut semble être trop.
Donc, si vous utilisez JUnit 5, vous pouvez utiliser WARNING
comme alternative à STRICT_STUBS
mais vous voulez généralement éviter que LENIENT
soit trop silencieux.
En plus de MockitoExtension
, la bibliothèque mockito-junit-jupiter
fournit @MockitoSettings
qui peut être utilisée au niveau de la méthode ainsi qu'au niveau de la classe.
Voici un exemple :
import Java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
@ExtendWith(MockitoExtension.class)
public class FooTest {
@MockitoSettings(strictness = Strictness.WARN)
@Test
void foo() throws Exception {
List<String> strings = Mockito.mock(List.class);
Mockito.when(strings.add("a"))
.thenReturn(true);
Mockito.when(strings.add("b"))
.thenReturn(false);
}
@Test
void fooKo() throws Exception {
List<String> strings = Mockito.mock(List.class);
Mockito.when(strings.add("a"))
.thenReturn(true);
Mockito.when(strings.add("b"))
.thenReturn(false);
}
}
fooKo()
lève l'exception Mockito de mauvaise utilisation pendant que foo()
réussit, mais fournit des avertissements utiles:
[MockitoHint] FooTest (voir javadoc pour MockitoHint): [MockitoHint] 1. Inutilisé -> sur FooTest.foo (FooTest.Java:19) [MockitoHint] 2. Inutilisé -> sur FooTest. foo (FooTest.Java:21)
Comme autre alternative, vous pouvez également utiliser Mockito.lenient()
très bien décrit par Aschoerk pour appliquer la rigueur indulgente à une invocation spécifique. Ainsi, vous pouvez définir chaque appel simulé comme indulgent lors de l’instanciation simulée:
@Test
void foo() throws Exception {
List<String> strings = Mockito.mock(List.class, Mockito.withSettings()
.lenient());
....
}