How to run #SpringBatchTest with junit5 jupiter engine - spring-boot

I am following below link to write end to end spring batch job test.
https://docs.spring.io/spring-batch/docs/current/reference/html/testing.html#endToEndTesting
This tells to annotate your test class with #RunWith(SpringRunner.class) which is Junit4 feature and my test case is working fine utilizing vintage engine of junit5.
Since my other non-batch related test cases are running on jupiter engine, I wanted to use same for my end to end spring batch job test. If I replace #RunWith(SpringRunner.class) with #ExtendWith(SpringExtension.class), I could see none of autowired fields got instantiated and have null value.
As I am new to both spring batch and jupiter, I am unable to understand what is going wrong.
Any pointer would be highly appreciated
Sample code of junit test case
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.JobRepositoryTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringRunner;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;
#Slf4j
#Testcontainers
#SpringBatchTest
#SpringBootTest
#RunWith(SpringRunner.class)
//#ExtendWith(SpringExtension.class)
#ContextConfiguration(initializers = {BatchJobConfigTest.Initializer.class})
#Import({LiquibaseConfigprimary.class, LiquibaseConfigsecondary.class})
public class BatchJobConfigTest {
#Container
private static final PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:latest")
.withDatabaseName("dummy-db")
.withUsername("sa")
.withPassword("sa");
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
#Autowired
#Qualifier("primaryDatasource")
private HikariDataSource primaryDatasource;
#Autowired
#Qualifier("secondaryDatasource")
private HikariDataSource secondaryDatasource;
#Autowired
private SecondaryRepository secondaryRepository;
#Autowired
private PrimaryRepository primaryRepository;
#Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
#Autowired
private BatchConfigurer batchConfigurer;
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#BeforeEach
void setup() {
secondaryRepository.deleteAll();
primaryRepository.deleteAll();
}
#Test
public void testJob() throws Exception {
assertThat(primaryRepository.findAll()).hasSize(1);
assertThat(secondaryRepository.findAll()).hasSize(0);
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
assertThat(secondaryRepository.findAll()).hasSize(1);
assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo("COMPLETED");
}
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
if (!postgreSQLContainer.isRunning())
postgreSQLContainer.start();
TestPropertyValues.of(
"spring.data.postgres.url=" + postgreSQLContainer.getJdbcUrl(),
"spring.data.postgres.username=" + postgreSQLContainer.getUsername(),
"spring.data.postgres.password=" + postgreSQLContainer.getPassword(),
"spring.data.postgres.host=localhost",
"spring.data.postgres.port=" + postgreSQLContainer.getFirstMappedPort(),
"spring.data.postgres.database=" + postgreSQLContainer.getDatabaseName()
).applyTo(configurableApplicationContext.getEnvironment());
TestPropertyValues.of(
"spring.datasource-secondary.jdbc-url=" + postgreSQLContainer.getJdbcUrl(),
"spring.datasource-secondary.username=" + postgreSQLContainer.getUsername(),
"spring.datasource-secondary.password=" + postgreSQLContainer.getPassword(),
"spring.datasource-secondary.driver-class-name=org.postgresql.Driver"
).applyTo(configurableApplicationContext.getEnvironment());
}
}
}

You can just get rid of #RunWith(SpringRunner.class). There is no need to add #ExtendWith(SpringExtension.class) because that's already added by #SpringBootTest - at least in current versions of Spring Boot.
What you then have to do is change:
import org.junit.Test;
into
import org.junit.jupiter.api.Test;
because that's what tells the JUnit platform to run Jupiter tests instead of Vintage tests. Hope that will resolve your problems.

Related

Spring Boot - unit test not detecting the controller test

I have written unit test for the controller class but when I run unit test using "mvn test" or directly from "SpringBootDemo1ApplicationTests" only test under "SpringBootDemo1ApplicationTests" class runs, and it doesn't pick up test from "BookControllerTest" class.
SpringBootDemo1ApplicationTests.java:
package com.sprboot.SpringBootDemo1;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
class SpringBootDemo1ApplicationTests {
#Test
void contextLoads() {
}
}
BookControllerTest.java
package com.sprboot.SpringBootDemo1;
import com.sprboot.SpringBootDemo1.controllers.BookController;
import com.sprboot.SpringBootDemo1.models.Book;
import com.sprboot.SpringBootDemo1.services.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.json.JacksonTester;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Optional;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
#AutoConfigureJsonTesters
#WebMvcTest(BookController.class)
#SpringBootTest
public class BookControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BookService bookService;
#Autowired
private JacksonTester<Book> jsonBook;
#Test
private void canRetriveBookById() throws Exception {
given(bookService.getBookByID(123)).willReturn(Optional.of(new Book(123, "test book")));
MockHttpServletResponse response = mockMvc.perform(
get("/getBookById/123")
.accept(MediaType.APPLICATION_JSON))
.andReturn().getResponse();
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
assertThat(response.getContentAsString()).isEqualTo(
jsonBook.write(new Book(1, "test book")).getJson()
);
}
private void canRetriveBookByIdV2() throws Exception {
given(bookService.getBookByID(123)).willReturn(Optional.of(new Book(123, "test book")));
MockHttpServletResponse response = mockMvc.perform(
get("/getBookById/123")
.accept(MediaType.APPLICATION_JSON))
.andReturn().getResponse();
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
assertThat(response.getContentAsString()).isEqualTo(
jsonBook.write(new Book(1, "test book")).getJson()
);
}
}
I was expecting test "canRetriveBookById" to run as well but only test "contextLoads" runs, not sure whats wrong here.
It's because your #Test method is private.
From the #Test annotations documentation:
#Test methods must not be private or static and must not return a value.

#Autowired not working inside testcontainers

I am using test containers to make integration tests with the MSSQLServer image and when I am trying to inject my repository I am recieving the following error:
lateinit property modelRepository has not been initialized
And this confuse me because I'm using the same path I used to create the test but using a postgress database in another project.
My test archive:
package *.models.integration
import *.portadapter.repository.ModelRepository
import *.builders.ModelBuilder
import org.junit.ClassRule
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.MSSQLServerContainer
import org.testcontainers.containers.startupcheck.MinimumDurationRunningStartupCheckStrategy
import org.testcontainers.junit.jupiter.Testcontainers
import java.time.Duration
import java.util.function.Supplier
#DataJpaTest
#Testcontainers
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ModelRepositoryTest {
#Autowired
private lateinit var modelRepository: ModelRepository
companion object {
private val hibernateDriver: Supplier<Any> = Supplier { "org.hibernate.dialect.SQLServerDialect" }
private val hibernateDll: Supplier<Any> = Supplier { "none" }
#ClassRule
#JvmField
val mssqlserver: MSSQLServerContainer<*> = MSSQLServerContainer("mcr.microsoft.com/mssql/server:2017-latest")
.withStartupCheckStrategy(MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(5)))
.acceptLicense()
#JvmStatic
#DynamicPropertySource
fun properties(registry: DynamicPropertyRegistry) {
registry.add("spring.datasource.url", mssqlserver::getJdbcUrl)
registry.add("spring.datasource.username", mssqlserver::getUsername)
registry.add("spring.datasource.password", mssqlserver::getPassword)
registry.add("spring.jpa.properties.hibernate.dialect", hibernateDriver)
registry.add("spring.jpa.hibernate.ddl-auto", hibernateDll)
mssqlserver.acceptLicense().start()
}
}
#Test
fun `it should save a model`() {
val model = ModelBuilder().createModel().get()
modelRepository.save(model)
val modelFound = modelRepository.findByCode(model.code)
assertEquals(model.code, modelFound.get().code)
}
}
My Repository:
package *.portadapter.repository
import *.model.domain.Model
import org.springframework.data.jpa.repository.JpaRepository
import java.util.*
interface ModelRepository: JpaRepository<Model, String> {
fun findByCode(code: String): Optional<Model>
}
Could anyone please help me
Make sure you are using the right classes from junit4 or junit5. If you are using junit4 then add #RunWith(SpringRunner.class)
As I mentioned in the comment above, imports from junit4 and junit5 are mixed in the test provided. Below you can find the examples
junit4
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.testcontainers.containers.MSSQLServerContainer;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
#RunWith(SpringRunner.class)
#DataJdbcTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class SqlserverDemoApplication3Tests {
#ClassRule
public static MSSQLServerContainer sqlserver = new MSSQLServerContainer("mcr.microsoft.com/mssql/server:2017-CU12")
.acceptLicense();
#DynamicPropertySource
static void sqlserverProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", sqlserver::getJdbcUrl);
registry.add("spring.datasource.username", sqlserver::getUsername);
registry.add("spring.datasource.password", sqlserver::getPassword);
}
#Autowired
private JdbcTemplate jdbcTemplate;
#Test
public void contextLoads() {
this.jdbcTemplate.update("insert into profile (name) values ('profile-1')");
List<Map<String, Object>> records = this.jdbcTemplate.queryForList("select * from profile");
assertThat(records).hasSize(1);
}
}
junit5
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
#Testcontainers
#DataJdbcTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class SqlserverDemoApplication2Tests {
#Container
private static MSSQLServerContainer sqlserver = new MSSQLServerContainer("mcr.microsoft.com/mssql/server:2017-CU12")
.acceptLicense();
#DynamicPropertySource
static void sqlserverProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", sqlserver::getJdbcUrl);
registry.add("spring.datasource.username", sqlserver::getUsername);
registry.add("spring.datasource.password", sqlserver::getPassword);
}
#Autowired
private JdbcTemplate jdbcTemplate;
#Test
void contextLoads() {
this.jdbcTemplate.update("insert into profile (name) values ('profile-1')");
List<Map<String, Object>> records = this.jdbcTemplate.queryForList("select * from profile");
assertThat(records).hasSize(1);
}
}

#Scheduled works in Spring Boot application but not in #SpringBootTest

What is the reason that the #Scheduled function runs in Spring Boot Application but not in test environment (#SpringBootTest) ?
I am following the tutorial at https://github.com/spring-guides/gs-scheduling-tasks, the repository's test runs fine, but mine #Scheduled function does not run for once in my test, although it is working fine in my Spring Boot application.
Is it because of the version of Junit (The tutorial is using Junit5 while I am using JUnit 4) ?
My purpose of this test is to check the correctness of the configuration of my scheduler.
Below is the code I replicated with difference in Junit version.
BootApplication.java
package com.itsedo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
// SecurityAutoConfiguration: exclude default spring security boot file.
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
#ComponentScan("com.itsedo")
#EnableScheduling
public class BootApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(BootApplication.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(BootApplication.class, args);
}
}
ScheduledTasks.java
package com.itsedo.scheduler;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
#Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("The time is now {}", dateFormat.format(new Date()));
}
}
File ScheduledTasksTest.java
package com.itsedo.test;
import org.awaitility.Duration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.junit4.SpringRunner;
import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import com.itsedo.scheduler.ScheduledTasks;
#RunWith(SpringRunner.class)
#SpringBootTest
public class ScheduledTasksTest {
#SpyBean
ScheduledTasks tasks;
#Test
public void reportCurrentTime() {
await().atMost(Duration.TEN_SECONDS).untilAsserted(() -> {
verify(tasks, atLeast(2)).reportCurrentTime();
});
}
}
Test Output
Running Spring Boot application

java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...)

Hey i have started learning spring-boot junit testing using spring boot Test framework at the time of creating the test case i am facing issues below .
how to resolve this issue.
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
at org.springframework.util.Assert.state(Assert.java:70)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.getOrFindConfigurationClasses(SpringBootTestContextBootstrapper.java:202)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.processMergedContextConfiguration(SpringBootTestContextBootstrapper.java:137)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:409)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildDefaultMergedContextConfiguration(AbstractTestContextBootstrapper.java:323)
this is my RentalCarSystemApplicationTests.java
package com.test.project.rentalcarsystem;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest
public class RentalCarSystemApplicationTests {
#Test
public void contextLoads()
{
}
}
this is my TestWebApp.java
package com.test.project.rentalcarsystem;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.springframework.web.context.WebApplicationContext;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
public class TestWebApp extends RentalCarSystemApplicationTests {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testEmployee() throws Exception {
mockMvc.perform(get("/car")).andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.setMilleage").value("24")).andExpect(jsonPath("$.setModelname").value("BMW"))
.andExpect(jsonPath("$.setSeating_capacity").value("5")).andExpect(jsonPath("$.setType").value("SUV"))
.andExpect(jsonPath("$.setCost").value("3000")).andExpect(jsonPath("$.setEmail").value("demo#gmail.com"))
.andExpect(jsonPath("$.setNumber").value("9845658789")).andExpect(jsonPath("$.setPincode").value(560036));
}
}
From the error logs it gives hint to use classes attribute for #SpringBootTest so
Change #SpringBootTest to #SpringBootTest(classes = Application.class)
Give a fully classified name of the class like below.
#SpringBootTest(classes=com.package.path.class)
Also refer this thread

Spring controller tests with mocks

So I'm having some issues coming up with a solution for a test.
This is the method I want to test(I'm new to this). It clears all fields on a web page each time it's loaded.
#RequestMapping("/addaddressform")
public String addAddressForm(HttpSession session)
{
session.removeAttribute("firstname");
session.removeAttribute("surname");
session.removeAttribute("phone");
session.removeAttribute("workno");
session.removeAttribute("homeno");
session.removeAttribute("address");
session.removeAttribute("email");
return "simpleforms/addContact";
}
and here's what i have so far for the test
package ControllerTests;
import java.text.AttributedCharacterIterator.Attribute;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration (classes = {SimpleFormsControllerTest.class})
public class SimpleFormsControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void addAddressForm_ExistingContactDetailsInForm_RemovalFromSession() throws Exception{
MockHttpSession mockSession = new MockHttpSession();
mockSession.putValue("firstname", "test");
mockSession.putValue("surname", "test");
mockSession.putValue("phone", "test");
mockSession.putValue("workno", "test");
mockSession.putValue("homeno", "test");
mockSession.putValue("address", "test");
mockSession.putValue("email", "test");
mockMvc.perform(get("simpleForms/addaddressform").session(mockSession));
}
As this is the first time I've ever had to do this kind of thing I don't have much clue where to go with this.
Then you have to do is only assert the values, e.g.:
assertNull(mockSession.getAttribute("firstname"));
If you want to make sure it is the case, you can do:
assertNotNull(mockSession.getAttribute("firstname"));
before you fire the GET request.

Resources