Mockito: initialisation de champ privé simulé

95

Comment puis-je simuler une variable de champ qui est initialisée en ligne?

class Test {
    private Person person = new Person();
    ...
    public void testMethod() {
        person.someMethod();
        ...
    }
}

Ici, je veux me moquer person.someMethod()en testant la Test.testMethod()méthode pour laquelle je dois me moquer de l'initialisation de la personvariable. Un indice?

Edit: je ne suis pas autorisé à modifier la classe Person.

Arun
la source
1
Ce lien pourrait vous être utile stackoverflow.com/questions/13645571/…
Popeye
2
Vous devez refactoriser votre code afin de pouvoir passer une maquette pour Person. Les options incluent l'ajout d'un constructeur pour ce faire ou l'ajout d'une méthode de définition.
Tim Biegeleisen

Réponses:

108

Mockito est livré avec une classe d'assistance pour vous faire économiser du code de plaque chauffante de réflexion:

import org.mockito.internal.util.reflection.Whitebox;

//...

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    Whitebox.setInternalState(underTest, "person", mockedPerson);
    // ...
}

Mise à jour: Malheureusement, l'équipe mockito a décidé de supprimer la classe dans Mockito 2. Vous êtes donc de retour à l'écriture de votre propre code passe-partout de réflexion, utilisez une autre bibliothèque (par exemple Apache Commons Lang ), ou dérobez simplement la classe Whitebox ( sous licence MIT ).

Mise à jour 2: JUnit 5 est livré avec ses propres classes ReflectionSupport et AnnotationSupport qui pourraient être utiles et vous éviter de tirer dans une autre bibliothèque.

Ralf
la source
J'espionne mon objet cible pour d'autres raisons et dans ce cas, lorsque mon objet est espion, je ne peux pas définir l'état interne de cette façon.
Arun
Pourquoi pas? Je l'utilise avec des espions. Créez une instance Person. Stub ce qui a besoin de stubbing, puis définissez-le sur votre instance de test.
Ralf
Attention: Whitebox est dans le internalpackage et ne semble plus fonctionner sur Mockito 2.6.2.
Nova
Vous pouvez utiliser FieldSetter.setField (). J'ai donné un exemple ci-dessous pour le même.
Raj Kumar
1
@Ralf Parce que la modification d'un nom de référence en Java doit toujours entraîner une erreur de compilation.
Joe Coder
69

Assez tard à la fête, mais j'ai été frappé ici et j'ai obtenu l'aide d'un ami. Le problème était de ne pas utiliser PowerMock. Cela fonctionne avec la dernière version de Mockito.

Mockito vient avec ça org.mockito.internal.util.reflection.FieldSetter.

Ce qu'il fait essentiellement, c'est vous aider à modifier les champs privés à l'aide de la réflexion.

Voici comment vous l'utilisez:

@Mock
private Person mockedPerson;
private Test underTest;

// ...

@Test
public void testMethod() {
    FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);
    // ...
    verify(mockedPerson).someMethod();
}

De cette façon, vous pouvez passer un objet fictif et le vérifier plus tard.

Voici la référence.

Raj Kumar
la source
FieldSettern'est plus disponible dans Mockito 2.x.
Ralf
4
@Ralf J'utilise la version mockito-core-2.15.0. Il est disponible là-bas. static.javadoc.io/org.mockito/mockito-core/2.0.15-beta/org/… . Toujours en version bêta.
Raj Kumar
1
@RajKumar Class#getDeclaredFieldaccepte le paramètre unique, donc les parenthèses doivent ressembler à ceci FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("person"), mockedPerson);. C'est ainsi que je me suis trompé et j'ai pensé que ce serait peut-être une bonne idée de le corriger dans votre exemple. Merci d'avoir répondu.
Dapeng Li
1
utiliser une API interne n'est pas la meilleure idée
David
@RajKumar Nice, mais comme Zimbo Rodger l'a mentionné: est-ce une bonne idée d'utiliser une API interne? Pourquoi est-il réellement interne? C'est tellement utile pour les tests.
Willi Mentzel
32

Si vous utilisez Spring Test, essayez org.springframework.test.util.ReflectionTestUtils

 ReflectionTestUtils.setField(testObject, "person", mockedPerson);
David
la source
1
Génial! Cela pourrait aider à créer un
Willi Mentzel
build.gradle.kts:testImplementation("org.springframework:spring-test:5.1.2.RELEASE")
Willi Mentzel
28

J'ai déjà trouvé la solution à ce problème que j'ai oublié de poster ici.

@RunWith(PowerMockRunner.class)
@PrepareForTest({ Test.class })
public class SampleTest {

@Mock
Person person;

@Test
public void testPrintName() throws Exception {
    PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);
    Test test= new Test();
    test.testMethod();
    }
}

Les points clés de cette solution sont:

  1. Exécution de mes cas de test avec PowerMockRunner: @RunWith(PowerMockRunner.class)

  2. Demandez à Powermock de se préparer Test.classà la manipulation des champs privés:@PrepareForTest({ Test.class })

  3. Et enfin moquez-vous du constructeur de la classe Person:

    PowerMockito.mockStatic(Person.class); PowerMockito.whenNew(Person.class).withNoArguments().thenReturn(person);

Arun
la source
8
Votre explication parle de la mockStaticfonction, mais cela n'est pas représenté dans votre exemple de code. L'exemple de code doit-il avoir l' mockStaticappel, ou n'est-ce pas obligatoire pour les constructeurs?
Shadoninja
10

Le code suivant peut être utilisé pour initialiser le mappeur dans la maquette du client REST. Le mapperchamp est privé et doit être défini lors de la configuration du test unitaire.

import org.mockito.internal.util.reflection.FieldSetter;

new FieldSetter(client, Client.class.getDeclaredField("mapper")).set(new Mapper());
Jarda Pavlíček
la source
5

En utilisant le guide de @ Jarda, vous pouvez définir ceci si vous devez définir la variable sur la même valeur pour tous les tests:

@Before
public void setClientMapper() throws NoSuchFieldException, SecurityException{
    FieldSetter.setField(client, client.getClass().getDeclaredField("mapper"), new Mapper());
}

Mais sachez que définir des valeurs privées différentes doit être manipulé avec précaution. S'ils sont privés, c'est pour une raison quelconque.

Exemple, je l'utilise, par exemple, pour modifier le temps d'attente d'un sommeil dans les tests unitaires. Dans les exemples réels, je veux dormir pendant 10 secondes, mais dans le test unitaire, je suis satisfait si c'est immédiat. Dans les tests d'intégration, vous devez tester la valeur réelle.

Hugo Dias
la source