This is @Autowired
, which I absolutely use when writing code in Spring.
Recently, it seems that constructor injection is recommended instead of field injection.
Try google to find out why constructor injection is recommended in the first place. There are various reasons, but the one I was most convinced of was "that? Isn't this a unit test effort?", So I wrote about it.
The sample is too good, but if there is a class that sends such an email.
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);
}
}
I will write the test code.
MailServiceTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class MailServiceTest {
@InjectMocks
MailService mailService;
@Mock
MailSender mailSender;
@Captor
ArgumentCaptor<SimpleMailMessage> msgCaptor;
@Test
public void test() {
//Executing the method under test
mailService.send();
//Among the methods under test, mailSender#send has been executed once
//Then capture the arguments
verify(mailSender, times(1)).send(msgCaptor.capture());
//Validate the contents of the 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."));
}
}
If it is a field injection class, is it written like this? I hope I'm used to it, but I forget how to write Captor every time, and if I don't comment (even if there is a comment), I can't read it later, and it's annoying in the first place.
MailService.java
public class MailService {
private final MailSender mailSender;
@Autowired
public MailService(MailSender mailSender) {
this.mailSender = mailSender;
}
//The following is omitted
@ RunWith
or @ SpringBootTest
.MailServiceTest.java
public class MailServiceTest {
/**
*Create your own mailSender mock class
*/
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() {
//Pass the mock class to create an instance
MailSenderMock mailSenderMock = new MailSenderMock();
MailService mailService = new MailService(mailSenderMock);
//Executing the method under test
mailService.send();
//Verification
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."));
}
}
In the sample, the mock class is written as an inner class, but you can create a class file individually. You can make it with Mockito.mock, though. It was the end of the story that Mockito was annoying, but ... (Mockito.mock () may be used for classes that do not have to do anything.)
The class under test in the sample is too simple, so it doesn't seem to make much difference, but I thought it would be easy not to mock it with Mockito. Also, since it no longer depends on SpringBoot, JUnit will not work due to property settings or lack of DB.
For example, you can have about 3 @Autowired
and change the behavior of only one for unit testing.
In this case, set it to @ SpringBootTest
and @Autowired
in the test class the class that does not change the behavior.
In this case, it depends on Spring Boot. I want to write test code like an integration test from Controller, but there is an obstructive class! At times, I think this method is also good.
MailServiceTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class MailServiceTest {
@Autowired
HogeSerice hogeService;
@Autowired
FugaService fugaService;
private class MailSenderMock {
//Abbreviation
}
@Test
public void test() {
//Pass the mock class to create an instance
MailSenderMock mailSenderMock = new MailSenderMock();
MailService mailService = new MailService(mailSenderMock, hogeService, fugaService);
//Abbreviation