web-dev-qa-db-fra.com

Mockito, JUnit et Spring

J'ai commencé à apprendre sur Mockito seulement aujourd'hui. J'ai écrit un test simple (avec JUnit, voir ci-dessous), mais je ne comprends pas comment utiliser un objet fantaisie dans les beans gérés de Spring. Qu’est-ce que meilleures pratiques pour travailler avec Spring? Comment devrais-je injecter une dépendance simulée à mon haricot?

Vous pouvez ignorer ceci jusqu’à retour à ma question.

Tout d’abord, c’est ce que j’ai appris ... Ceci est un très bon article Mocks n’ont pas de talons qui explique les bases (contrôles de Mock vérification du comportement} _ pas vérification de l’état) . Ensuite, il y a un bon exemple ici Mockito et ici Plus facile de se moquer de mockito . Nous avons l'explication que les objets fictifs de Mockito sont tous deux mock et stub

Ici Mockito et ici Matchers , vous pouvez trouver plus d'exemples. 

Ce test

@Test
public void testReal(){
    List<String> mockedList = mock(List.class);
     //stubbing
     //when(mockedList.get(0)).thenReturn("first");

    mockedList.get(anyInt());
    OngoingStubbing<String> stub= when(null);
    stub.thenReturn("first");

    //String res = mockedList.get(0);
                //System.out.println(res);

     //you can also verify using argument matcher
     //verify(mockedList).get(anyInt());

    verify(mockedList);
    mockedList.get(anyInt());
}

fonctionne très bien.

Retour à ma question. Ici L'injection de Mockito se moque d'un bean Spring quelqu'un essaie d'utiliser Springs ReflectionTestUtils.setField(), mais alors ici Tests d'intégration Spring, création d'objets Mock recommandation recommandée à changer le contexte de Spring.

Je n'ai pas vraiment compris les deux derniers liens ... Quelqu'un peut-il m'expliquer quel problème le printemps a-t-il avec Mockito? Quel est le problème avec cette solution?

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

@Before
public void setup() {
        MockitoAnnotations.initMocks(this);
}

https://stackoverflow.com/a/8742745/1137529

EDIT: Je n'étais pas très clair. Je vais fournir 3 exemples de code pour clarifier mon état d'esprit: Supposons que nous ayons le bean HelloWorld avec la méthode printHello() et le bean HelloFacade avec la méthode sayHello qui transmettent les appels à la méthode printHello() de HelloWorld.

Le premier exemple consiste à utiliser le contexte de Spring, sans runner personnalisé, à l'aide de ReflectionTestUtils pour l'injection de dépendance (DI):

public class Hello1Test  {
private ApplicationContext ctx;

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
    this.ctx = new ClassPathXmlApplicationContext("META-INF/spring/ServicesImplContext.xml");
}



@Test
public void testHelloFacade() {
    HelloFacade obj = (HelloFacade) ctx.getBean(HelloFacadeImpl.class);
    HelloWorld mock = mock(HelloWorld.class);
    doNothing().when(mock).printHello();

    ReflectionTestUtils.setField(obj, "hello", mock);
    obj.sayHello();

    verify(mock, times(1)).printHello();
}

}

Comme l'a souligné @Noam, il existe un moyen de l'exécuter sans appeler explicitement MockitoAnnotations.initMocks(this);. J'abandonnerai également l'utilisation du contexte Spring dans cet exemple.

@RunWith(MockitoJUnitRunner.class)
public class Hello1aTest {


@InjectMocks
private HelloFacade obj =  new HelloFacadeImpl();

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Une autre façon de faire ça

public class Hello1aTest {

@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);
}


@InjectMocks
private HelloFacadeImpl obj;

@Mock
private HelloWorld mock;


@Test
public void testHelloFacade() {
    doNothing().when(mock).printHello();
    obj.sayHello();
}

}

Noth, que dans l'exemple précédent, nous devons installer manuellement HelloFacadeImpl et l'assigner à HelloFacade, car HelloFacade est une interface. Dans le dernier exemple, nous pouvons simplement déclarer HelloFacadeImpl et Mokito l'instanciera pour nous. L'inconvénient de cette approche, à savoir que l'unité sous test est maintenant impl-class et non interface.

73
alexsmail

Honnêtement, je ne suis pas sûr de bien comprendre votre question: P Je vais essayer de clarifier autant que je peux, à partir de ce que je tire de votre question initiale:

Premièrement, dans la plupart des cas, vous ne devriez PAS avoir d’inquiétude pour le printemps. Il est rarement nécessaire que Spring soit impliqué dans la rédaction de votre test unitaire. Dans des cas normaux, il vous suffit d'instancier le système testé (SUT, la cible à tester) dans votre test unitaire et d'injecter des dépendances de SUT dans le test également. Les dépendances sont généralement une maquette/un moignon.

Votre façon proposée à l’origine, et l’exemple 2, 3 fait précisément ce que je décris ci-dessus.

Dans de rares cas (tels que des tests d'intégration ou des tests unitaires spéciaux), vous devez créer un contexte d'application Spring et extraire votre SUT du contexte d'application. Dans ce cas, je pense que vous pouvez:

1) Créez votre SEP au printemps dans l'application ctx, obtenez une référence et injectez-vous des répliques

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Before
    /* Initialized mocks */
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void someTest() {
         // ....
    }
}

ou 

2) suivez la procédure décrite dans votre lien Spring Integration Tests, Creating Mock Objects . Cette approche consiste à créer des simulacres dans le contexte de l'application Spring, et vous pouvez demander à l'objet simulé de l'application ctx d'effectuer votre stubbing/vérification:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

Les deux manières devraient fonctionner. La principale différence est que, dans le premier cas, les dépendances sont injectées après le cycle de vie du printemps, etc. (par exemple, l'initialisation du bean), tandis que le dernier cas est injecté à l'avance. Par exemple, si votre SUT implémente InitializingBean de spring et que la routine d'initialisation implique les dépendances, vous verrez la différence entre ces deux approches. Je crois qu'il n'y a pas de bon ou de mauvais pour ces 2 approches, tant que vous savez ce que vous faites.

Juste un supplément, @Mock, @Inject, MocktoJunitRunner, etc. est inutile pour utiliser Mockito. Ce sont juste des utilitaires pour vous sauver en tapant le Mockito.mock (Foo.class) et un tas d’invocations de setter.

50
Adrian Shum

Votre question semble porter sur lequel des trois exemples que vous avez donnés est l'approche privilégiée.

Exemple 1 utiliser Reflection TestUtils n'est pas une bonne approche pour Unit testing. Vous ne voulez vraiment pas charger le contexte printanier pour un test unitaire. Juste se moquer et injecter ce qui est nécessaire, comme le montrent vos autres exemples. 

Vous voulez charger le contexte de printemps si vous voulez effectuer des tests Integration, mais je préférerais utiliser @RunWith(SpringJUnit4ClassRunner.class) pour effectuer le chargement du contexte avec @Autowired si vous avez besoin d'accéder explicitement à ses beans.

Exemple 2 est une approche valide et l'utilisation de @RunWith(MockitoJUnitRunner.class) supprimera le besoin de spécifier une méthode @Before et un appel explicite à MockitoAnnotations.initMocks(this);.

Exemple 3 est une autre approche valide qui n'utilise pas @RunWith(...). Vous n'avez pas explicitement instancié votre classe sous test HelloFacadeImpl, mais vous auriez pu faire de même avec l'exemple 2.

Ma suggestion est d'utiliser l'exemple 2 pour vos tests unitaires car cela réduit l'encombrement du code. Vous pouvez revenir à la configuration plus détaillée si et quand vous y êtes obligé.

6
Brad

L’introduction de nouvelles fonctionnalités de test dans Spring 4.2.RC1 permet d’écrire des tests d’intégration Spring qui ne reposent pas sur le SpringJUnit4ClassRunner. Découvrez this une partie de la documentation.

Dans votre cas, vous pouvez écrire votre test d'intégration Spring et continuer à utiliser des simulacres comme ceci:

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Autowired
    @InjectMocks
    TestTarget sut;

    @Mock
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}
4
geoand

Voici mon court résumé. 

Si vous voulez écrire un test unitaire, n'utilisez pas un champ applicationContext de Spring, car vous ne souhaitez pas que de vraies dépendances soient injectées dans la classe que vous testez. À la place, utilisez mocks, soit avec l'annotation @RunWith(MockitoJUnitRunner.class) au-dessus de la classe, soit avec MockitoAnnotations.initMocks(this) dans la méthode @Before.

Si vous voulez écrire un test d'intégration, utilisez:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourTestApplicationContext.xml")

Pour configurer le contexte de votre application avec une base de données en mémoire, par exemple, . Normalement, vous n'utilisez pas de simulation dans les tests d'intégration, mais vous pouvez le faire en utilisant l'approche MockitoAnnotations.initMocks(this) décrite ci-dessus.

2
Adriaan Koster

Si vous utilisez mockito 1.9 (ou une version plus récente), vous n'avez pas vraiment besoin de la MockitoAnnotations.initMocks(this);.

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

L'annotation @InjectMocks injectera toutes vos simulations dans l'objet MyTestObject.

2
Noam

Si vous souhaitez migrer votre projet vers Spring Boot 1.4, vous pouvez utiliser la nouvelle annotation @MockBean pour simuler MyDependentObject. Avec cette fonctionnalité, vous pouvez supprimer les annotations @Mock et @InjectMocks de Mockito de votre test. 

0
luboskrnac

La différence selon que vous devez instancier votre champ annoté @InjectMocks réside dans la version de Mockito et non dans le fait que vous utilisiez MockitoJunitRunner ou MockitoAnnotations.initMocks. En 1.9, qui traitera également une injection de constructeur de vos champs @Mock, il effectuera l'instanciation pour vous. Dans les versions précédentes, vous devez instancier vous-même.

Voici comment je teste un peu mes haricots Spring. Il n'y a pas de problème. Les gens sont dans la confusion lorsqu'ils veulent utiliser les fichiers de configuration Spring pour effectuer l'injection des modèles, ce qui constitue un point critique entre les tests unitaires et les tests d'intégration.

Et bien sûr l’unité testée est une Impl. Vous devez tester une vraie chose concrète, non? Même si vous le déclariez comme une interface, vous deviez instancier la vraie chose pour la tester. Maintenant, vous pouvez entrer dans les espions, qui sont des wrappers factices/fictifs autour d'objets réels, mais cela devrait être le cas.

0
jhericks