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:
commons (module, packaging:jar, utils module)
+--- src
+--- pom.xml
proposal (module, packaging:pom)
proposal-api (submodule: interfaces, dto, packaging:jar)
proposal-mapping (submodule: entities)
proposal-service (submodule: services, spring data repositories, dto - entity<->dto mappers, depends on proposal-api and proposal-mapping packaging:jar)
+--- src
+---main
+--- java
+---com.company.proposal.service
+--- DeviceRepositoryService.java
+---
DeviceMapper.java
+---
ProposalRepositoryService.java
+---
ProposalMapper.java
+---
and much more classes...
+--- test
+--- java
+---com.company.proposal.service
+---
DeviceRepositoryServiceTest.java
+---
ProposalRepositoryServiceTest.java
+---
...
+--- pom.xml
proposal-starter (submodule: autoconfiguration classes, packaging:jar)
+--- src
+---main
+--- java
+---com.company.proposal.configuration
+--- ProposalAutoConfiguration.java
+--- RemoteReportProcessorAutoConfiguration.java
+--- other configuration classes...
+---resources
+---META-INF
+--- spring.factories
+---application.properties
+--- pom.xml
entry-point (module, packaging: pom)
entry-point-api (submodule, packaging: jar)
entry-point-service (submodule, packaging: jar)
entry-point-starter (submodule, packaging: war deployed on wildfly)
other-modules ...
pom.xml (root pom)
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);
}
}
Related
I'm having an issue trying to make test in my spring-boot project.
architecture
as you can see, my project is devided with maven modules.
"alta-launcher" is the "main" project getting every other module as dependencies.
The problem is that my tests in the user module won't get the spring context so my fields "Autowired" will be null on runtime.
Any ideas how to configure this project to be able to do tests in each modules ?
Edit :
#SpringBootTest(classes = CoreApplication.class)
class UserQueryTransformerImplTest {
#Autowired
private UserQueryTransformer userQueryTransformer;
#Test
void toDTO() {
UserEntity userEntity = new UserEntity();
userEntity.setLogin("everest");
userEntity.setFirstName("Everest");
userEntity.setLastName("Mountain");
UserQueryDTO userQueryDTO = userQueryTransformer.toDTO(userEntity);
assertEquals(userEntity.getLogin(), userQueryDTO.getLogin());
assertEquals(userEntity.getFirstName(), userQueryDTO.getFirstName());
assertEquals(userEntity.getLastName(), userQueryDTO.getLastName());
}
The annotation #SpringBootTest with the attribute classes is unusable because I don't have access to the launcher module from user module. And without the attribute (juste #SprinBootTest) is when I'm getting my autowire field null which makes sense cause I don't have the context.
I was able to fix the issue by using ContextConfiguration annotation.
(ExtendWith is for the junit5 part)
#ContextConfiguration(classes = ConfigurationTest.class)
#ExtendWith(SpringExtension.class)
class UserQueryTransformerImplTest {
#Autowired
private UserQueryTransformer userQueryTransformer;
#Test
void toDTO() {
UserEntity userEntity = new UserEntity();
userEntity.setLogin("everest");
userEntity.setFirstName("Everest");
userEntity.setLastName("Mountain");
UserQueryDTO userQueryDTO = userQueryTransformer.toDTO(userEntity);
assertEquals(userEntity.getLogin(), userQueryDTO.getLogin());
assertEquals(userEntity.getFirstName(), userQueryDTO.getFirstName());
assertEquals(userEntity.getLastName(), userQueryDTO.getLastName());
}
and here is my configurationTest.java
#TestConfiguration
public class ConfigurationTest {
#Bean
UserQueryTransformer createUserQueryTransformer() {
return new UserQueryTransformerImpl();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
UserQueryDTO createUserQueryDTO() {
return new UserQueryDTO();
}
You can recreate your custom context in tests, try something like this:
#ContextConfiguration
#RunWith(SpringRunner.class)
class UserQueryTransformerImplTest {
#TestConfiguration
static class Config {
#Bean
public UserQueryTransformer uqt() {
return new UserQueryTransformer();
}
}
#Autowired
private UserQueryTransformer userQueryTransformer;
#Test
void toDTO() {
UserEntity userEntity = new UserEntity();
userEntity.setLogin("everest");
userEntity.setFirstName("Everest");
userEntity.setLastName("Mountain");
UserQueryDTO userQueryDTO = userQueryTransformer.toDTO(userEntity);
assertEquals(userEntity.getLogin(), userQueryDTO.getLogin());
assertEquals(userEntity.getFirstName(), userQueryDTO.getFirstName());
assertEquals(userEntity.getLastName(), userQueryDTO.getLastName());
}
I try to test my spring app but encounter following problem:
In "normal mode"(mvn spring-boot:run) the app starts as expected and adapterConfig gets set and is NOT NULL. When I start my testclass to test the MVC, adapterConfig does not get set. Spring ignores the whole config class.
test:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = StudentController.class)
public class StudentControllerTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private StudentService service;
#MockBean
private StudentRepository repository;
#Test
public void shouldReturnABC() throws Exception{
MvcResult result = this.mockMvc.perform(get("/students/abc")).andReturn();
}
}
controller:
#RestController
#RequestMapping("/students")
#PermitAll
public class StudentController {
#Autowired
StudentService studentService;
//get
#GetMapping("/abc")
public String abc (){
return "abc";
}
config:
#Configuration
public class SpringBootKeycloakConfigResolver implements KeycloakConfigResolver {
private KeycloakDeployment keycloakDeployment;
private AdapterConfig adapterConfig;
#Autowired
public SpringBootKeycloakConfigResolver(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
#Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
if (keycloakDeployment != null) {
return keycloakDeployment;
}
keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfig);
return keycloakDeployment;
}
}
adapterConfig is null when hitting the test but gets set & created when hitting it the normal way, any idea?
Using #WebMvcTest, the container will inject only components related to Spring MVC (#Controller, #ControllerAdvice, etc.) not the full configuration use #SpringBootTest with #AutoConfigureMockMvc instead.
Spring Boot Javadoc
Keycloak's AutoConfiguration is not included by #WebMvcTest.
You could
Include it manually via #Import(org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration.class)
Or use #SpringBootTest
with spring boot 2.5 i had I had to import KeycloakAutoConfiguration into my test.
#WebMvcTest(value = ApplicationController.class, properties = "spring.profiles.active:test")
#Import(KeycloakAutoConfiguration.class)
public class WebLayerTest {
// ... test code ....
}
I'd like to test my repository method. However when I run my test, it fails because of UnsatisfiedDependencyException. It for some reason tries to create AuthorizationServerConfig (or other bean if I remove #Configuration annotation from this one). It fails cause deeper in dependencies chain it requires RabbitMQ connection pool, that I prefer to not provide in repository test.
The question is why Spring tries to create all those beans not linked to repository logic?
I attempted to exclude all those beans with #DataMongoTest(exludeFilters: ...) and #DataMongoTest(exludeAutoConfiguration: ...) but it had no effect.
The only thing that helped was to add #Profile("!test") to all beans (all! controllers, services, components) in an application, but it smells like a very ugly solution.
Repository class is very simple:
#Repository
public interface ParticipantRepository extends MongoRepository<Participant, String> {
List<Participant> findAllByLoggedInTrueAndAccessTokenExpirationAfter(Date expirationAfter);
}
My test class:
#DataMongoTest()
#RunWith(SpringRunner.class)
public class ParticipantRepositoryTest {
#Autowired
private MongoTemplate mongoTemplate;
#Autowired
private ParticipantRepository repository;
private List<Participant> participants;
#Before
public void setUp() throws Exception {
participants = createParticipants();
repository.saveAll(participants);
}
#After
public void tearDown() throws Exception {
repository.deleteAll();
}
#Test
public void findAllByLoggedInTrueAndExpirationAfter_shouldNotReturnLoggedOutParticipants() {
List<Participant> result = repository.findAllByLoggedInTrueAndAccessTokenExpirationAfter(new Date());
getLoggedOutParticipants().forEach(participant -> assertThat(participant, not(isIn(result))));
}
...
}
In an Axon-SpringBoot App I have an aggregate that uses an injected DAO in some of its command handlers.
For instance:
#Aggregate
class MyAggregate {
#CommandHandler
public MyAggregate (CreateMyAggregateCommand command, #Autowired MyAggregateDao dao) {
final SomeProperty = command.getSomePoprtery();
if (dao.findBySomeProperty(someProperty) == null) {
AggregateLifeCycle.apply(
new MyAggregateCreatedEvent(command.getIdentifier(),
someProperty);
} else {
// Don't create, already exits with some property
// report ...
}
}
}
A standard test like
#Test
void creationSucceeds () {
aggregateTestFixture = new AggregateTestFixture<>(MyAggregate.class);
final CreateMyAggregateCommand command = new CreateMyAggregateCommand(...);
final MyAggregateCreatedEvent = new MyAggregateCreatedEvent(...);
aggregateTestFixture
.givenNoPriorActivity()
.when(command)
.expectEvents(event);
}
fails with:
org.axonframework.test.FixtureExecutionException: No resource of type
[com.xmpl.MyAggregateDao] has been registered. It is required
for one of the handlers being executed.
How can I provide a test implementation?
Solution 1: Mocking
Since this is about unit testing and my question involves database calls (external service), mocking seems applicable as long as testing is about isolated aggregate behavior only.
#Test
void creationSucceeds () {
aggregateTestFixture = new AggregateTestFixture<>(MyAggregate.class);
aggregateTestFixture.registerInjectableResource(
Mockito.mock(MyAggregateDao.class));
}
Solution 2: Real Injection (jUnit 5)
This one works for me:
Get small library for Spring jUnit5 testing support from this github repo
Annotate test classes:
#SpringBootTest(classes = {SpringTestConfig.class})
#ExtendWith(SpringExtension.class)
public class MyAggregateTest {
// ...
}
Place application.properties in src/test/resources
Write Spring Test Configuration which starts a fully functioning Spring container:
#EnableAutoConfiguration
public class SpringTestConfig {
// Set up whatever you need
#Bean
#Autowired
MyAggregateDao myDao (DataSource dataSource) {
// ...
}
#Bean
#Autowired
EventStorageEngine eventStorageEngine () {
return new InMemoryEventStorageEngine();
}
}
Inject directly into your tests, configure AggregateTestFixture
private AggregateTestFixture<MyAggregate> aggregateTestFixture;
#Autowired
private MyAggregateDao myDao;
#BeforeEach
void beforeEach () {
aggregateTestFixture = new AggregateTestFixture<>(MyAggregate.class);
// We still need to register resources manually
aggregateTestFixture.registerInjectableResource(myDao);
}
With jUnit 4
Setting up a test configuration that starts a Spring container with jUnit 4 is a bit different but there's enough documentation out there. Start here
I have an application built with JHipster which contains several tests.
I created a simple configuration class that instantiate a bean connected to an external service as such :
#Configuration
public class KurentoConfiguration {
#Bean(name = "kurentoClient")
public KurentoClient getKurentoClient(#Autowired ApplicationProperties applicationProperties) {
return KurentoClient.create(applicationProperties.getKurento().getWsUrl());
}
}
But as you would guess, this code crash during testing because the external service is not up but this code is still run during application context loading.
So I need to create a "stateless" version of this bean to be used during testing.
Here is a simple example of a test that fail because of my configuration :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Face2FaceApp.class)
public class LogsResourceIntTest {
private MockMvc restLogsMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
LogsResource logsResource = new LogsResource();
this.restLogsMockMvc = MockMvcBuilders
.standaloneSetup(logsResource)
.build();
}
#Test
public void getAllLogs()throws Exception {
restLogsMockMvc.perform(get("/management/logs"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE));
}
}
What is the solution to make this bean not highly dependent of an external service during unit testing ?
You can use the MockBean annotation in your test to replace your existing bean :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Face2FaceApp.class)
public class LogsResourceIntTest {
#MockBean
private KurentoClient kurentoClient;
private MockMvc restLogsMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
LogsResource logsResource = new LogsResource();
this.restLogsMockMvc = MockMvcBuilders
.standaloneSetup(logsResource)
.build();
given(kurentoClient.someCall()).willReturn("mock");
}
....
}
Here is the Spring Boot documentation :
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-mocking-beans
Thanks to the help of everyone here, I managed to solve this problem :
I created an interface of KurentoClient and implemented a proxy that call KurentoClient methods
My "normal" #Bean kurentoClient returns the implemented proxy
I writed a #TestConfiguration (UnitTestConfiguration) and added a #Bean with the same signature as the one crated above but this one returns mockito's mock(KurentoClient.class)
I created a class TestBase that every test class extends and which
contains
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class)
#ContextConfiguration(classes = UnitTestConfiguration.class)
public class TestBase {
}