I am beginner with spring framework. I have a problem with configuring unit tests in spring boot, more precisely with loading spring context while running unit tests. I work with maven multimodule project (in team) and looking for the right solution to do this. Part of my project structure is as follows:
Example unit test written by me (DeviceRepositoryServiceTest.java):
@RunWith(SpringRunner.class)
public class DeviceRepositoryServiceTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@MockBean
private DeviceRepository deviceRepository;
@Autowired
private DeviceMapper deviceMapper;
private DeviceRepositoryService deviceRepositoryService;
private final String imei = "123456789123456";
private final String producer = "samsung";
private final String model = "s5";
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
deviceRepositoryService = new DeviceRepositoryService(deviceRepository, deviceMapper);
}
@org.springframework.boot.test.context.TestConfiguration
static class TestConfiguration {
@Bean
public DeviceMapper deviceMapper() {
return new DeviceMapperImpl();
}
}
@Test
public void test_should_create_device() {
given(deviceRepository.findByImei(imei)).willReturn(null);
when(deviceRepository.save(any(Device.class))).thenAnswer((Answer) invocation -> invocation.getArguments()[0]);
DeviceSnapshot device = deviceRepositoryService.createOrFindDeviceByImei(imei, producer, model);
assertThat(device.getImei()).isEqualTo(imei);
assertThat(device.getProducer()).isEqualTo(producer);
assertThat(device.getModel()).isEqualTo(model);
verify(deviceRepository, times(1)).save(any(Device.class));
}
@Test
public void test_should_return_device() {
Device testDevice = createTestDevice();
given(deviceRepository.findByImei(imei)).willReturn(testDevice);
DeviceSnapshot actualDevice = deviceRepositoryService
.createOrFindDeviceByImei(testDevice.getImei(), testDevice.getProducer(), testDevice.getModel());
assertThat(actualDevice.getImei()).isEqualTo(testDevice.getImei());
assertThat(actualDevice.getProducer()).isEqualTo(testDevice.getProducer());
assertThat(actualDevice.getModel()).isEqualTo(testDevice.getModel());
verify(deviceRepository, times(0)).save(any(Device.class));
verify(deviceRepository, times(1)).findByImei(testDevice.getImei());
}
@Test
public void test_should_find_device() {
Device device = createTestDevice();
given(deviceRepository.findOne(device.getId())).willReturn(device);
DeviceSnapshot actualDevice = deviceRepositoryService.findDeviceById(device.getId());
DeviceSnapshot expectedDevice = deviceMapper.toDeviceSnapshot(device);
assertThat(actualDevice).isEqualTo(expectedDevice);
verify(deviceRepository, times(1)).findOne(device.getId());
}
@Test
public void test_should_find_device_by_pparams() {
Device device = createTestDevice();
Long proposalId = 1L, providerConfigId = 2L;
given(deviceRepository.findByProposalParams(proposalId, providerConfigId)).willReturn(device);
DeviceSnapshot actualDevice = deviceRepositoryService.findDeviceByProposalParams(proposalId, providerConfigId);
DeviceSnapshot expectedDevice = deviceMapper.toDeviceSnapshot(device);
assertThat(actualDevice).isEqualTo(expectedDevice);
verify(deviceRepository, times(1)).findByProposalParams(proposalId, providerConfigId);
}
@Test
public void test_should_throw_not_found_1() {
given(deviceRepository.findOne(anyLong())).willReturn(null);
this.thrown.expect(DeviceNotFoundException.class);
deviceRepositoryService.findDeviceById(1L);
}
@Test
public void test_should_throw_not_found_2() {
given(deviceRepository.findByProposalParams(anyLong(), anyLong())).willReturn(null);
this.thrown.expect(DeviceNotFoundException.class);
deviceRepositoryService.findDeviceByProposalParams(1L, 1L);
}
private Device createTestDevice() {
return Device.builder()
.id(1L)
.imei(imei)
.model(model)
.producer(producer)
.build();
}
}
As you can see I use @TestConfiguration annotation to define context, but because class DeviceRepositoryService
is quite simple - only 2 dependencies so context definition is also simple. I also have to test class ProposalRepositoryService
which looks as follows in short:
@Slf4j
@Service
@AllArgsConstructor
@Transactional
public class ProposalRepositoryService implements ProposalService {
private final ProposalRepository proposalRepository;
private final ProposalMapper proposalMapper;
private final ProposalRepositoryProperties repositoryProperties;
private final ImageProposalRepository imageProposalRepository;
private final ProviderConfigService providerConfigService;
...
}
In above class is more dependencies and the thing is I don't want to write bunch of configuration code for every test (TestConfiguration annotation). Eg. If I add some dependency to some service I would have to change half of my unit tests classes, also a lot of code repeats itself. I have also example when unit test code is getting ugly because of configuration definition:
@TestPropertySource("classpath:application-test.properties")
public class RemoteReportProcessorRepositoryServiceTest {
@Autowired
private RemoteReportProcessorRepositoryService remoteReportProcessorRepositoryService;
@TestConfiguration //here, I don't want to write bunch of configuration code for every test
static class TestConfig {
@Bean
@Autowired
public RemoteReportProcessorRepositoryService remoteReportProcessorRepositoryService(RemoteReportMailService remoteReportMailService,
FtpsService ftpsService,
RemoteDailyReportProperties remoteDailyReportProperties,
RemoteMonthlyReportProperties remoteMonthlyReportProperties,
DeviceRepository deviceRepository,
ProposalRepository proposalRepository) {
return new RemoteReportProcessorRepositoryService(ftpsService, remoteReportMailService, remoteDailyReportProperties, remoteMonthlyReportProperties, deviceRepository, proposalRepository);
}
@Bean
@Autowired
public FtpsManagerService ftpsManagerService(FTPSClient ftpsClient, MailService mailService, FtpsProperties ftpsProperties) {
return new FtpsManagerService(ftpsClient, ftpsProperties, mailService);
}
@Bean
public FTPSClient ftpsClient() {
return new FTPSClient();
}
@Bean
@Autowired
public MailService mailService(MailProperties mailProperties, JavaMailSender javaMailSender, PgpProperties pgpProperties) {
return new MailManagerService(mailProperties, javaMailSender, pgpProperties);
}
@Bean
public JavaMailSender javaMailSender() {
return new JavaMailSenderImpl();
}
@Bean
@Autowired
public RemoteReportMailService remoteReportMailService(RemoteReportMailProperties remoteReportMailProperties,
JavaMailSender javaMailSender,
Session session,
PgpProperties pgpProperties) {
return new RemoteReportMailManagerService(remoteReportMailProperties, javaMailSender, session, pgpProperties);
}
@Bean
@Autowired
public Session getJavaMailReceiver(RemoteReportMailProperties remoteReportMailProperties) {
Properties properties = new Properties();
properties.put("mail.imap.host", remoteReportMailProperties.getImapHost());
properties.put("mail.imap.port", remoteReportMailProperties.getImapPort());
properties.setProperty("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
properties.setProperty("mail.imap.socketFactory.fallback", "false");
properties.setProperty("mail.imap.socketFactory.port", remoteReportMailProperties.getImapPort().toString());
properties.put("mail.imap.debug", "true");
properties.put("mail.imap.ssl.trust", "*");
return Session.getDefaultInstance(properties);
}
}
...
}
So, my question is how to configure spring context for unit testing in spring boot maven multimodule project the right way, without writing bunch of configuration code? I also will be grateful for the links to the articles when is describe in detail how to deal with maven multimodule projects.
After reading various articles and posts eg. Is it OK to use SpringRunner in unit tests? I realized that I don't need the entire application context when running tests, instead I should mock bean dependencies using plain @Mock
annotation if testing without even involving and loading spring application context (which is faster). However, If I need some slice of application context (eg. to automatically load test properties or just for integration tests) then I use spring boot annotations prepared for that: @WebMvcTest
@JpaTest
@SpringBootTest
and so on.
Examples:
Plain Mock Test (without involving spring):
public class UserServiceImplTest {
@Mock
private UserRepository userRepository;
private UserServiceImpl userService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
userService = new UserServiceImpl(userRepository);
}
/* Some tests here */
}
Test with slice of spring context:
@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@EnableConfigurationProperties(value = DecisionProposalProperties.class)
@SpringBootTest(classes = {
DecisionProposalRepositoryService.class,
DecisionProposalMapperImpl.class
})
public class DecisionProposalRepositoryServiceTest {
@MockBean
private DecisionProposalRepository decisionProposalRepository;
@MockBean
private CommentRepository commentRepository;
@Autowired
private DecisionProposalRepositoryService decisionProposalRepositoryService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
/* Some tests here */
}
Data jpa test:
@RunWith(SpringRunner.class)
@DataJpaTest
public class ImageProposalRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private ImageProposalRepository imageProposalRepository;
@Test
public void testFindOne() throws Exception {
ImageProposal imageProposal = ImageProposal.builder()
.size(1024)
.filePath("/test/file/path").build();
entityManager.persist(imageProposal);
ImageProposal foundImageProposal = imageProposalRepository.findOne(imageProposal.getId());
assertThat(foundImageProposal).isEqualTo(imageProposal);
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.