Spring boot test doesn't Autowire all dependencies - spring-boot

I have a bit of a confusing error in my test scenario.
We want to refactor an Application that is not tested at all. To ensure that we have the same outcame after refactoring I'll write some integration tests for one Controller class.
#RestController
#RequestMapping("/rfq")
public class RfqController {
#Autowired
private RfqRepository rfqRepo;
#Autowired
private RfqDao rfqDao;
...
#PostMapping("/get")
public #ResponseBody BuyerRfqView getRFQ(#RequestBody SingleIdBody body) {
int id = body.getId();
Optional<Rfq> rfq = rfqRepo.getById(id);
...
}
}
In that case I want to test with testcontainers and spring-boot-test everything worked well, containers are up and running and the application starts so far. But the problem is that at runtime the spring-boot-test doesn't Autowire rfqRepo in the class under test. In the Testclass, every single dependency is in the ComponentScan or EntityScan and the repositories are also injected. I have no clue why this is not working. when the test is running I get a Nullpointer Exception by rfqRepo ...
here is the Test class:
#SpringBootTest(classes = RfqController.class, webEnvironment =
SpringBootTest.WebEnvironment.RANDOM_PORT)
#ComponentScan({...})
#EnableJpaRepositories({...})
#EntityScan({...})
#EnableAutoConfiguration
#ActiveProfiles("local")
#Testcontainers
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class RfqControllerTest {
#Container
private static OracleContainer database = new OracleContainer(
"oracleinanutshell/oracle-xe-11g:latest")
.withExposedPorts(1521, 5500)
.withPassword("...");
#InjectMocks
RfqController rfqController;
#DynamicPropertySource
static void databaseProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", database::getJdbcUrl);
registry.add("spring.datasource.username", database::getUsername);
registry.add("spring.datasource.password", database::getPassword);
}
#BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
ScriptUtils.runInitScript(new JdbcDatabaseDelegate(database, ""), "ddl.sql");
}
#Test
void testGetRFQ() {
BuyerRfqView result = rfqController.getRFQ(new SingleIdBody(176501));
Assertions.assertEquals(new BuyerRfqView(), result);
}
}

In the SpringBootTest annotation you are only using RfqController. That's the only class then that is available during test.
#SpringBootTest(classes = RfqController.class, webEnvironment =SpringBootTest.WebEnvironment.RANDOM_PORT)
So you have to add all classes that are needed for your tests.

Related

Spring Boot JUnit tests fail with Status expected:<200> but was:<404>

For some time I've been struggling to make JUnit tests for my rest controller. For some reason, every time I try to run them I get the error Status expected:<200> but was:<404>. Here is my controller:
#RestController
#RequestMapping("/travels")
#RequiredArgsConstructor
public class TravelController {
private final TravelService travelService;
private final TravelOutputDtoMapper travelOutputDtoMapper;
#GetMapping
public List<TravelOutputDto> getAll() {
List<Travel> travels = travelService.getAll();
return travels.stream()
.map(travelOutputDtoMapper::travelToTravelOutputDto)
.collect(Collectors.toList());
}
}
And here is my test:
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = TravelController.class)
#ContextConfiguration(classes = {
TravelOutputDtoMapper.class,
TravelOutputDtoMapperImpl.class
})
class TravelControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TravelService travelService;
#Autowired
private TravelOutputDtoMapper travelOutputDtoMapper;
#Test
void testGetAll() throws Exception {
List<Travel> travels = mockTravelList();
Mockito.when(travelService.getAll()).thenReturn(travels);
mockMvc.perform(get("/travels"))
.andExpect(status().isOk());
}
private List<Travel> mockTravelList() {
// Dummy travel list
}
}
I think the reason is connected with TravelOutputDtoMapper as if I remove it from the controller and don't try to inject it the tests are passing, but I cannot find any information why it is doing it. The autowired mapper has an instance and works just fine.
Here is the Mapper:
#Mapper(componentModel = "spring")
public interface TravelOutputDtoMapper {
#Mapping(target = "from", source = "entity.from.code")
#Mapping(target = "to", source = "entity.to.code")
TravelOutputDto travelToTravelOutputDto(Travel entity);
}
The #ContextConfiguration annotation is used for a different purpose:
#ContextConfiguration defines class-level metadata that is used to determine how to load and configure an ApplicationContext for integration tests.
Using Spring Boot and #WebMvcTest there's no need to manually specify how to load the context. That's done for you in the background.
If you'd use this annotation, you'd specify your main Spring Boot class here (your entry-point class with the #SpringBootApplication annotation).
From what I can see in your test and your question is that you want to provide an actual bean for the TravelOutputDtoMapper, but mock the TravelService.
In this case, you can use #TestConfiguration to add further beans to your sliced Spring TestContext:
// #ExtendWith(SpringExtension.class) can be removed. This extension is already registered with #WebMvcTest
#WebMvcTest(controllers = TravelController.class)
class TravelControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TravelService travelService;
#Autowired
private TravelOutputDtoMapper travelOutputDtoMapper;
#TestConfiguration
static class TestConfig {
#Bean
public TravelOutputDtoMapper travelOutputDtoMapper() {
return new TravelOutputDtoMapper(); // I assume your mapper has no collaborators
}
}
// ... your MockMvc tests
}

Spring 2 + JUnit 5, share #MockBean for entire test suite

I create a Spring 2.3 application using Spring Data REST, Hibernate, Mysql.
I created my tests, I've around 450 tests splitted in about 70 files. Because the persistence layer leans on a multi tenant approach (single db per tenant) using a Hikari connection pool, I've the need to avoid the pool is initializated for each test file but at the same time I need to use #MockBean because I need to mock up some repositories in the entire Spring test contest.
I create a custom annotation for all test in my suite:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#SpringBootTest
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public #interface TestConfig {
}
Reading many posts and the doc, I know if I use #MockBean inside a test, the Spring context is reloaded and therefore a new pool connection is created in my case.
My idea is to create a #MockBean and share it with all tests in my suite so the context is not reloaded every time.
I tried several approaches:
#Log4j2
public class TestExecutionListener extends AbstractTestExecutionListener implements Ordered {
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
try {
TestDbUtils testDbUtils = (TestDbUtils) testContext.getApplicationContext().getBean(TestDbUtils.class);
testDbUtils.truncateDB();
TenantRepository tenantRepository = mock(TenantRepository.class);
testContext.setAttribute("tenantRepository", tenantRepository);
TenantContext.setCurrentTenantId("test");
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
} catch (Exception e) {
}
}
#Override
public int getOrder() {
return Integer.MAX_VALUE;
}
}
All my tests are annotated like this:
#TestConfig
#Log4j2
public class InvoiceTests {
#Test
public void test1(){
}
}
Unfortunately my tenantRepository.findByTenantId() is not mocked up. I also tried to create an abstract superclass:
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public abstract class AbstractIntegrationTest {
#MockBean
protected TenantRepository tenantRepository;
#MockBean
protected SubscriptionRepository subscriptionRepository;
#Autowired
protected TestDbUtils testDbUtils;
#BeforeAll
public void beforeAll() {
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
}
#BeforeEach
public void setup() {
testDbUtils.truncateDB();
TenantContext.setCurrentTenantId("test");
}
}
Even if my tests extended this superclass, during the run all of them were skipped (not sure why).
Is there any way to accomplish what I described?

Geting java.lang.IllegalStateException: Duplicate mock definition while using #MockBean in test case

I have one service class that I want to mock but while running the test I am Getting Caused by: java.lang.IllegalStateException: Duplicate mock definition [MockDefinition#482ba4b1 name = '', typeToMock = com.service.ThirdPartyService, extraInterfaces = set[[empty]], answer = RETURNS_DEFAULTS, serializable = false, reset = AFTER]
I have tried to create mock service using #MockBean at class level, field level, and used #Qualifier as well to resolve the issue
#Service
public class ThirdPartyService{
.......................
public String decrypt(String encryptedText) {
//third party SDK I am using
return Service.decrypt.apply(encryptedText);
}
.........
..............
}
#ComponentScan("com")
#PropertySource({"classpath:/api.properties", "classpath:/common.properties"})
#SpringBootConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#Transactional
public class TestControllerTest extends IntegrationTest {
#MockBean
ThirdPartyService thirdPartyService;
#Before
public void initMocks(){
MockitoAnnotations.initMocks(this);
}
#Test
public void test() throws Exception {
when(ts.decrypt("encryptedText")).thenReturn("decryptedText")
Request req = Request.builder().name("name123").build();
//written performPost method in some other class
ResultActions action = performPost("/test", req);
action.andExpect(status().isOk());
}
}
public class IntegrationTest {
protected final Gson mapper = new Gson();
private MockMvc mvc;
#Autowired
private WebApplicationContext context;
public ObjectMapper objectMapper = new ObjectMapper();
#Before
public void setup() {
this.mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}}
When I am calling Thirdparty service decrypt method then it should return me decryptedText as a string. But getting duplicate mock definition error
I had the same issue.
Cause of this were test configuration file which was put somewhere else and it contained the mocked bean.
I have solved this by using #Autowired instead of #MockBean as this will result in autowiring the already mocked bean.
In my case the problem appeared after another dependency update and the reason was in the #SpringBootTest annotation referencing the same class twice:
#SpringBootTest(classes = {MyApplication.class, ApiControllerIT.class})
class ApiControllerIT extends IntegrationTestConfigurer {
// ...
}
#SpringBootTest(classes = {MyApplication.class, TestRestTemplateConfiguration.class})
public class IntegrationTestConfigurer {
// ...
}
I fixed it by removing #SpringBootTest annotation from the child class (ApiControllerIT).
In my case it was incorrect test class name that doesn't end with 'Test'.
If you have nested test classes try this:
#NestedTestConfiguration(OVERRIDE)
From Spring release notes: https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-5.x#upgrading-to-version-53

SpringBoot Junit testing main method

I have below test for my spring boot main method.
The test tries to start the application 2 times which is expected.
First time when it starts the application it uses the Mock object hewever 2nd time it starts the application it calls the actual bean.
I have ReferenceDataService having #PostConstract method call which makes rest call to some other application which I don't want in my tests.
Another thing is that MqConfiguration which tries to connect to IBM queues that also I would like to avoid in my test.
Please note even though I have added #ComponentScan(excludeFilters... in my test class it does not exclude it.
How do I write test for my main method in this case?
#ActiveProfiles(profiles = {"test"})
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MainApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"camel.springboot.java-routes-include-pattern=**/NONE*"})
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, SecurityAutoConfiguration.class})
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
#ComponentScan(excludeFilters = {#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {MqConfiguration.class, ReferenceDataCache.class})})
public class MainApplicationTest {
#MockBean
private MqService mqService;
#MockBean
private ReferenceDataService referenceDataService;
#SpyBean
private ReferenceDataCache cacheSpy;
#Test
public void test() {
Mockito.when(referenceDataService.getCurrencies()).thenReturn(new HashMap<>());
Mockito.when(referenceDataService.getFrequencies()).thenReturn(null);
Mockito.when(referenceDataService.getDayCountTypes()).thenReturn(null);
Mockito.when(referenceDataService.getBusinessDayConverntions()).thenReturn(null);
Mockito.when(referenceDataService.getRateDefinations()).thenReturn(null);
Mockito.when(referenceDataService.getBusinessCalendar()).thenReturn(null);
Mockito.when(referenceDataService.getFinancingTypes()).thenReturn(null);
Mockito.when(referenceDataService.getStaffs()).thenReturn(null);
MainApplication.main(new String[]{});
}
}
MainApplication.java (The class to be tested)
#SpringBootApplication
#EnableJms
#EnableCaching
#AutoConfigureBefore(JmsAutoConfiguration.class)
public class MainApplication {
private static final Logger logger = LoggerFactory.getLogger(MainApplication.class);
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
One could split it into two separate testing parts as we should strive to test a single functionality per test (Single Responsibility Principle). You could model your testing like below:
#Test
public void applicationContextLoadedTest(){
}
#Test
public void applicationStartTest() {
//you can add your mocks as per your required dependencies and requirements
MainApplication.main(new String[] {});
}
Alternatively, if you are allowed to use PowerMockito, then the following link gives you a working example for verifying static invocations.PowerMockito - SpringBoot test

Cannot find spring application class in unit test class

We are in the process of creating from the scratch product with spring webflux. We are writing our unit test cases. Though I can able to get the Spring Application main class in my import, when we run mvn clean install, it is keep on telling that Compilation failure, cannot find class. How we can overcome this?
My project structure is,
Application
-app-web-module
-src/java/com/org/SpringApplicationClass
-pom.xml
-app-web-unitcases
-src/test/com/org/mytestclass
-pom.xml
And my test class is,
#TestPropertySource(properties = "CONFIG_ENVIRONMENT=ci")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#ContextConfiguration(classes = com.org.MainApplication.class)
public class BaseACTest {
#Autowired
private WebTestClient webTestClient;
#BeforeAll
public void init1() {
MockitoAnnotations.initMocks(this);
}
#BeforeEach
public void init() {
Assertions.assertNotNull(webTestClient);
}
#Test
public void testGetAllAmenities() {
webTestClient.get().uri("/urltobeplaced/1234")
.header("X-Request-ID", "123")
.header("X-Session-ID", "123")
.header("X-Application-ID", "123")
.exchange()
.expectStatus().isOk();
}
}

Resources