92 votes

Injection du champ privé @Autowired pendant les tests

J'ai une configuration de composant qui est essentiellement un lanceur pour une application. Il est configuré de la manière suivante :

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    //other methods
}

MyService est annoté avec la balise @Service Spring et s'intègre automatiquement dans ma classe de lancement sans aucun problème.

Je voudrais écrire quelques cas de test jUnit pour MyLauncher, pour ce faire j'ai commencé une classe comme ceci :

public class MyLauncherTest
    private MyLauncher myLauncher = new MyLauncher();

    @Test
    public void someTest() {

    }
}

Puis-je créer un objet Mock pour MyService et l'injecter dans myLauncher dans ma classe de test ? Actuellement, je n'ai pas de getter ou setter dans myLauncher car Spring gère l'auto-câblage. Si possible, j'aimerais ne pas avoir à ajouter de getters et setters. Puis-je dire au scénario de test d'injecter un objet fantaisie dans la variable autowired en utilisant une balise @Before méthode d'init ?

Si je m'y prends mal, n'hésitez pas à le dire. Je suis encore novice en la matière. Mon objectif principal est de disposer d'un code Java ou d'une annotation qui place un objet fantaisie dans l'environnement de travail. @Autowired sans que je doive écrire une méthode de paramétrage ou utiliser une méthode de type applicationContext-test.xml fichier. Je préfère de loin maintenir tout ce qui concerne les scénarios de test dans le fichier .java au lieu de devoir maintenir un contenu d'application séparé juste pour mes tests.

J'espère utiliser Mockito pour les objets fantaisie. Dans le passé, j'ai fait cela en utilisant org.mockito.Mockito et en créant mes objets avec Mockito.mock(MyClass.class) .

93voto

Manuel Quinones Points 4068

Vous pouvez absolument injecter des mocks sur MyLauncher dans votre test. Je suis sûr que si vous montrez quel framework de mocking vous utilisez, quelqu'un sera prompt à fournir une réponse. Avec mockito, je chercherais à utiliser @RunWith(MockitoJUnitRunner.class) et à utiliser @RunWith(MockitoJUnitRunner.class). [ ]

@RunWith(MockitoJUnitRunner.class)
public class MyLauncherTest
    @InjectMocks
    private MyLauncher myLauncher = new MyLauncher();

    @Mock
    private MyService myService;

    @Test
    public void someTest() {

    }
}

74voto

Pierre Henry Points 1940

La réponse acceptée (utiliser MockitoJUnitRunner y @InjectMocks ) est formidable. Mais si vous voulez quelque chose d'un peu plus léger (pas de runner JUnit spécial), et de moins "magique" (plus transparent), notamment pour une utilisation occasionnelle, vous pouvez simplement définir les champs privés directement en utilisant l'introspection.

Si vous utilisez Spring, vous disposez déjà d'une classe utilitaire pour cela : org.springframework.test.util.ReflectionTestUtils

L'utilisation est assez simple :

ReflectionTestUtils.setField(myLauncher, "myService", myService);

Le premier argument est votre haricot cible, le second est le nom du champ (généralement privé), et le dernier est la valeur à injecter.

Si vous n'utilisez pas Spring, il est assez trivial d'implémenter une telle méthode utilitaire. Voici le code que j'utilisais avant de trouver cette classe Spring :

public static void setPrivateField(Object target, String fieldName, Object value){
        try{
            Field privateField = target.getClass().getDeclaredField(fieldName);
            privateField.setAccessible(true);
            privateField.set(target, value);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }

3voto

snydergd Points 11

Je pense que pour que le câblage automatique fonctionne sur votre classe MyLauncher (pour myService), vous devrez laisser Spring l'initialiser au lieu d'appeler le constructeur, en câblant automatiquement myLauncher. Une fois que cette classe est auto-câblée (et que myService est également auto-câblé), Spring (1.4.0 et plus) fournit une annotation @MockBean que vous pouvez mettre dans votre test. Ceci remplacera un seul beans correspondant dans le contexte avec un mock de ce type. Vous pouvez ensuite définir plus précisément le mocking que vous souhaitez, dans une méthode @Before.

public class MyLauncherTest
    @MockBean
    private MyService myService;

    @Autowired
    private MyLauncher myLauncher;

    @Before
    private void setupMockBean() {
        doNothing().when(myService).someVoidMethod();
        doReturn("Some Value").when(myService).someStringMethod();
    }

    @Test
    public void someTest() {
        myLauncher.doSomething();
    }
}

Votre classe MyLauncher peut alors rester inchangée, et votre bean MyService sera un mock dont les méthodes renvoient les valeurs que vous avez définies :

@Component
public class MyLauncher {
    @Autowired
    MyService myService;

    public void doSomething() {
        myService.someVoidMethod();
        myService.someMethodThatCallsSomeStringMethod();
    }

    //other methods
}

Cette méthode présente quelques avantages par rapport aux autres méthodes mentionnées :

  1. Vous n'avez pas besoin d'injecter manuellement myService.
  2. Vous n'avez pas besoin d'utiliser le runner ou les règles de Mockito.

2voto

kodmanyagha Points 96

Je suis un nouvel utilisateur de Spring. J'ai trouvé une solution différente pour cela. En utilisant la réflexion et en rendant publics les champs nécessaires et en assignant des objets fantaisie.

C'est mon contrôleur d'authentification et il a quelques propriétés privées Autowired.

@RestController
public class AuthController {

    @Autowired
    private UsersDAOInterface usersDao;

    @Autowired
    private TokensDAOInterface tokensDao;

    @RequestMapping(path = "/auth/getToken", method = RequestMethod.POST)
    public @ResponseBody Object getToken(@RequestParam String username,
            @RequestParam String password) {
        User user = usersDao.getLoginUser(username, password);

        if (user == null)
            return new ErrorResult("Kullancad veya ifre hatal");

        Token token = new Token();
        token.setTokenId("aergaerg");
        token.setUserId(1);
        token.setInsertDatetime(new Date());
        return token;
    }
}

Et voici mon test Junit pour AuthController. Je crée des propriétés publiques et privées, je leur attribue des objets fantaisie et je me lance :)

public class AuthControllerTest {

    @Test
    public void getToken() {
        try {
            UsersDAO mockUsersDao = mock(UsersDAO.class);
            TokensDAO mockTokensDao = mock(TokensDAO.class);

            User dummyUser = new User();
            dummyUser.setId(10);
            dummyUser.setUsername("nixarsoft");
            dummyUser.setTopId(0);

            when(mockUsersDao.getLoginUser(Matchers.anyString(), Matchers.anyString())) //
                    .thenReturn(dummyUser);

            AuthController ctrl = new AuthController();

            Field usersDaoField = ctrl.getClass().getDeclaredField("usersDao");
            usersDaoField.setAccessible(true);
            usersDaoField.set(ctrl, mockUsersDao);

            Field tokensDaoField = ctrl.getClass().getDeclaredField("tokensDao");
            tokensDaoField.setAccessible(true);
            tokensDaoField.set(ctrl, mockTokensDao);

            Token t = (Token) ctrl.getToken("test", "aergaeg");

            Assert.assertNotNull(t);

        } catch (Exception ex) {
            System.out.println(ex);
        }
    }

}

Je ne connais pas les avantages et les inconvénients de cette méthode, mais elle fonctionne. Cette technique a un peu plus de code mais ces codes peuvent être séparés par différentes méthodes, etc. Il y a d'autres bonnes réponses à cette question, mais je veux mettre l'accent sur une solution différente. Désolé pour mon mauvais anglais. Je vous souhaite une bonne java à tous :)

1voto

Regarde ça. enlace

Ensuite, écrivez votre scénario de test comme

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/applicationContext.xml"})
public class MyLauncherTest{

@Resource
private MyLauncher myLauncher ;

   @Test
   public void someTest() {
       //test code
   }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X