[JAVA] Les tests unitaires ne sont-ils pas plus faciles avec l'injection de constructeur? Parler

introduction

C'est @ Autowired, que j'utilise absolument lors de l'écriture de code au printemps. Récemment, il semble que l'injection de constructeur soit recommandée au lieu de l'injection de champ.

Essayez google pour savoir pourquoi l'injection de constructeur est recommandée en premier lieu. Il y a plusieurs raisons, mais celle dont j'étais le plus convaincu était "ça? N'est-ce pas un effort de test unitaire?", J'ai donc écrit à ce sujet.

C'est ennuyeux de se moquer

Classe à tester

L'échantillon est trop adapté, mais s'il existe une classe qui envoie un tel e-mail.

MailService.java


import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

@RestController
@RequestMapping(path="/mail")
public class MailService {

    @Autowired
    MailSender mailSender;

    @PostMapping
    public void send() {

        SimpleMailMessage msg = new SimpleMailMessage();
        msg.setFrom("[email protected]");
        msg.setTo("[email protected]");
        msg.setSubject("Hello World");
        msg.setText("Welcome to my world.");
       
        mailSender.send(msg);
    }
}

Code de test

J'écrirai le code de test.

MailServiceTest.java


@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class MailServiceTest {

    @InjectMocks
    MailService mailService;

    @Mock
    MailSender mailSender;

    @Captor
    ArgumentCaptor<SimpleMailMessage> msgCaptor;

    @Test
    public void test() {
        //Exécution de la méthode testée
        mailService.send();

        //Parmi les méthodes testées, mailSender#send a été exécuté une fois
        //Au fait, saisissez l'argument
        verify(mailSender, times(1)).send(msgCaptor.capture());

        //Validez le contenu de l'argument
        SimpleMailMessage msg = msgCaptor.getValue();
        assertThat(msg.getFrom(), is("[email protected]"));
        assertNotNull(msg.getTo());
        assertThat(msg.getTo().length, is(1));
        assertThat(msg.getTo()[0], is("[email protected]"));
        assertThat(msg.getSubject(), is("Hello World"));
        assertThat(msg.getText(), is("Welcome to my world."));
    }
}

Dans une classe avec injection de champ, serait-il écrit comme ça? J'espère y être habitué, mais j'oublie à chaque fois comment écrire Captor, et si je ne fais pas de commentaire (même s'il y a un commentaire), je ne peux pas le lire plus tard, et c'est ennuyeux en premier lieu.

Essayez l'injection de constructeur

Classe à tester (uniquement corrections)

MailService.java


public class MailService {

    private final MailSender mailSender;

    @Autowired
    public MailService(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    //Ce qui suit est omis

Code de test

MailServiceTest.java


public class MailServiceTest {

    /**
     *Créez votre propre classe simulée mailSender
     */
    private class MailSenderMock implements MailSender {
        SimpleMailMessage simpleMessage;
        @Override
        public void send(SimpleMailMessage simpleMessage) throws MailException {
            this.simpleMessage = simpleMessage;
        }
        @Override
        public void send(SimpleMailMessage... simpleMessages) throws MailException {
        }
    }

    @Test
    public void test() {

        //Passez la classe fictive pour créer une instance
        MailSenderMock mailSenderMock = new MailSenderMock();
        MailService mailService = new MailService(mailSenderMock);

        //Exécution de la méthode testée
        mailService.send();

        //Vérification
        SimpleMailMessage msg = mailSenderMock.simpleMessage;
        assertThat(msg.getFrom(), is("[email protected]"));
        assertNotNull(msg.getTo());
        assertThat(msg.getTo().length, is(1));
        assertThat(msg.getTo()[0], is("[email protected]"));
        assertThat(msg.getSubject(), is("Hello World"));
        assertThat(msg.getText(), is("Welcome to my world."));
    }
}

Dans l'exemple, la classe fictive est écrite en tant que classe interne, mais vous pouvez créer un fichier de classe individuellement. Vous pouvez le faire avec Mockito.mock, cependant. C'était la fin de l'histoire que Mockito était ennuyeux, mais ... (Mockito.mock () peut être bon pour les classes où vous n'avez rien à faire.)

Il semble qu'il n'y ait pas beaucoup de différence car la classe cible de test de l'échantillon est trop simple, mais j'ai pensé qu'il serait facile de ne pas se moquer d'elle avec Mockito. De plus, comme il ne dépend plus de SpringBoot, JUnit ne fonctionnera pas en raison des paramètres de propriété ou du manque de base de données.

Une telle chose

Par exemple, vous pouvez avoir environ 3 @ Autowired et changer le comportement pour un seul test unitaire. Dans ce cas, définissez-le sur @ SpringBootTest et définissez la classe qui ne modifie pas le comportement sur @ Autowired dans la classe de test.

Dans ce cas, cela dépend de Spring Boot. Je veux écrire du code de test comme un test d'intégration de Controller, mais il y a une classe qui me gêne! Parfois, je pense que cette méthode est également bonne.

MailServiceTest.java


@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class MailServiceTest {
    @Autowired
    HogeSerice hogeService;
    @Autowired
    FugaService fugaService;

    private class MailSenderMock {
        //Abréviation
    }

    @Test
    public void test() {

        //Passez la classe fictive pour créer une instance
        MailSenderMock mailSenderMock = new MailSenderMock();
        MailService mailService = new MailService(mailSenderMock, hogeService, fugaService);
    //Abréviation

Recommended Posts

Les tests unitaires ne sont-ils pas plus faciles avec l'injection de constructeur? Parler
Test avec com.google.testing.compile
[Java] Comment omettre l'injection de constructeur de ressort avec Lombok