What kind of test shall I use in my spring project(and am I doing it right)? - spring

My project is a simple management system for shop which is connected to MySQL database.
I only have JUnit test like this one(is this test written correctly?):
#RunWith(SpringRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class EmployeeRepositoryIntegrationTest
{
#Autowired
private TestEntityManager entityManager;
#Autowired
private EmployeeRepository employeeRepository;
#Test
public void whenFindByLastName_thenReturnEmployee()
{
User employee = new User("UserName","password","John",
"Smith","Adress Test","123123123","CityTest");
entityManager.persist(employee);
entityManager.flush();
User result = userRepository.findUserByLastName(employee.getLastName()).get(0);
assertThat(result.getLastName()).isEqualTo(employee.getLastName());
}
}
Should I add Mockito Tests, and what kind of test should I add in future?

I would avoid Mockito to test you JPA repoistories.
Better to seed an in-memory database (which can be spun up and torn down) with real test data.
If you're using the #DataJpaTest annotation you're expected to use sql to seed your database, look at this #DataJpaTest example. You can use sql files and get rid of the TestEntityManager.
Take a look at this DBUnit example. You add your test data in an XML file in your resources directory.

Related

PostConstruct and test

I use spring boot 3
Main spring boot class
#EnableTransactionManagement
#SpringBootApplication
#Slf4j
public class FlexApplication{
private final ApplicationParameterManager appParamManager;
public FlexApplication(ApplicationParameterManager appParamManager) {
this.appParamManager = appParamManager;
}
#PostConstruct
public void init(){
}
....
}
#Service
#Slf4j
public class ApplicationParameterManager{
....
}
Basic test
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
Otherwise this test, application run well
I get this error
Parameter 0 of constructor in com.acme.FlexApplication required a bean
of type 'com.acme.parameter.ApplicationParameterManager' that could
not be found.
I think it is not related to version of Spring Boot.
As you're using #DataJpaTest , your bean is not created
Spring Docs:
#DataJpaTest can be used if you want to test JPA applications. By
default it will configure an in-memory embedded database, scan for
#Entity classes and configure Spring Data JPA repositories. Regular
#Component beans will not be loaded into the ApplicationContext.
Solution would be to use #SpringBootTest instead of #DataJpaTest if your test is not really a JPA test.
Also, still using #DataJpaTest you could add #Import(ApplicationParameterManager.class) to your test class
When using #DataJpaTest, you are not creating the whole spring context as when you run the application normally but you only create the beans responsible for data access layer.
In order to run your tests properly, you need to provide a mocked bean of type ApplicationParameterManager.
The easiest way to do it is by utilizing #MockBean annotation.
So, to make your tests work, edit the test in the following way.
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
#DataJpaTest
public class ListUserRepositoryTest {
#MockBean
private ApplicationParameterManager applicationParameterManager;
#Autowired
private ListUserRepository repository;
#Test
public void getListUserByUserType(){
String typeUser = "CETEST";
Pageable page = Pageable.ofSize(10);
Page<ListUser> pageListUser = repository.findAllByTypeUser(typeUser, page);
assertThat(pageListUser.getContent().size() > 5 ).isTrue();
}
}
That way, the spring context will include a mocked bean of your required dependency.
Take a look at #MockBean java doc for more information. https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
If you prefer to run the whole spring context in order to perform full integration tests, take a look at #SpringBootTest annotation.
#DataJpaTest should be used when you want to test data access layer in isolation.

JUnit 5/Spring Boot - Use Properties but Mock Dependencies

I'm trying to write unit tests for a DAO/Repository. My intent is to mock the NamedParameterJdbcTemplate object within and verify that the surrounding business logic doesn't do any bad (i.e.: handles nulls, etc...).
I'd also like to verify that the SQL is combined correctly. I can do this with doAnswer(...), but the SQL queries live in a .properties file.
Is there a way to load a .properties file for testing, without loading all of the other dependencies of this class?
What I've Tried
I've tried decorating the Test class with various permutations of :
#ExtendWith(SpringExtension.class)
#ExtendWith(MockitoExtension.class)
#ContextConfiguration(classes = WidgetRepositoryImpl.class)
#TestPropertySource(properties = "sql.properties")
However, turning these on and off always seems to have one of the following effects:
Load the NamedParameterJdbcTemplate as a bean. This could work if I specified the nearby AppConfig class in the ContextConfiguration annotation, but then I'd need to load all of its underlying dependencies, and the whole point is to mock this field.
Load the Repository and mock the NamedParameterJdbcTemplate, but not load sql.properties. The SQL statements are now null.
I want to avoid ReflectionTestUtils.setField(widgetRepository, "namedParameterJdbcTemplate", "<put the sql here>"). This works, but there are many SQL queries (the code below is very simplified). It also introduces a risk of human error (since now we're testing strings in the Test class, not the actual properties file.
The question
Is there any way to load sql.properties for this class, but not attempt to load the NamedParameterJdbcTemplate?
Unit Test
#ExtendWith(SpringExtension.class)
#TestPropertySource(properties = "sql.properties")
class WidgetRepositoryImplTest {
#Mock
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
#Autowired
private WidgetRepository widgetRepository;
#Test
public void testGetWidgetById() {
// build a mock that returns a fake widget, and stores the SQL string for other tests
doAnswer(invocation -> { ...omitted for brevity... }).when(namedParameterJdbcTemplate).query(anyString(), anyMap(), any(WidgetMapper.class));
Widget widget = widgetRepository.getWidgetById("asdf-1234");
assertNotNull(widget);
}
Repository
#PropertySource("classpath:sql.properties")
#Slf4j
#Repository
public class WidgetRepositoryImpl implements WidgetRepository {
#Value("${widget-sql.selects.select-widget-by-id}")
private String selectWidgetByIdQuery;
#Autowired private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
#Override
public Widget getWidgetById(String id) {
Map<String, String> params = new HashMap<>();
params.put("widgetId", id);
List<Widget> results = namedParameterJdbcTemplate.query(selectWidgetByIdQuery, params, new WidgetMapper());
if (results.isEmpty()) {
return null;
}
return results.get(0);
}
sql.properties
widget-sql.selects.select-widget-by-id=select * from [WIDGETS] where WIDGET_ID=:widgetId
# a few dozen additional queries in here
The solution is to use SpringExtension with the #MockBean annotation. This annotation will generate a mock implementation of the given class and provide it as a Spring bean. This means that if your class uses #Autowired, Spring will inject the mock for you:
#ExtendWith(SpringExtension.class) // Make sure to use SpringExtension
#TestPropertySource(properties = "sql.properties")
class WidgetRepositoryImplTest {
#MockBean // Replace #Mock with #MockBean
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
#Autowired
private WidgetRepository widgetRepository;
// ...
}

Hibernate #Formula not working in Spring boot Tests when using #DataJpaTest

I'm trying to write an integration test for a java spring project.
At some point in the code I need to check customer.getId(), where the ID is created by a #Formula annotation like this: (keep in mind Customer_Id is my auto-incrementing primary key)
#Formula("Customer_Id")
private int id
I use lombok to generate getters and setters methods. My database for testing is H2 and I use Junit5 for my tests. The problem i'm facing is that i cannot test the getId() behavior because it's always 0 when I annotate the tests with #DataJpaTest. I made it work with #SpringBootTest but I would like to stick to sliced tests.
Is there a way to make #DataJpaTest work with #Formulas ?
Thanks in advance.
#Entity(name="customers")
#Table(name="Customers")
#Getter
#Setter
#NoArgsConstructor
public class Customer{
#Formula("Customer_Id")
private int id;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="Customer_Id", unique=true, nullable=false, precision=10)
private int customerId;
}
// if it is annotated with #SpringBootTest the tests passes
#DataJpaTest
#ActiveProfiles("test")
#EnableJpaAuditing
class MyClassTests {
#Autowired
private CustomerRepository customerRepository;
#Test
void given_a_customer_should_have_customerId_and_id_equal(){
Customer customer = new Customer();
customerRepository.save(customer);
// if i don't call findOneByCustomerId test fails for both #SpringBootTest and #DataJpaTest
customer = customerRepository.findOneByCustomerId(customer.getCustomerId());
assertEquals(customer.getCustomerId(), customer.getId()); // expected: 1, actual 0
}
}
If you use #DataJpaTest
By default, data JPA tests are transactional and roll back at the end
of each test. See the relevant section in the Spring Framework
Reference Documentation for more details
If you use #SpringBootTest that is not the case.
But, if you add #Transational to your Class, the test will fail.
Solution:
#DataJpaTest
#Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyClassTests {
...
Propagation.NOT_SUPPORTED states that if a physical transaction
exists, then it will be suspended before continuing. This physical
transaction will be automatically resumed at the end. After this
transaction is resumed, it can be rolled back (in case of a failure)
or committed.

How to test repository with junit5 and testcontainers?

I have a sample project in which I experiment with different technologies.
I have the following setup:
Spring Boot 2.3.4.RELEASE
Flyway 7.0.1
Testcontainers 1.15.0-rc2
Junit 5.7.0
How can I test the Repository layer with testcontainer-junit5?
Example of code I have now for CompanyRepositoryTest.java:
#ExtendWith(SpringExtension.class)
#Testcontainers
public class CompanyRepositoryTest {
#Autowired
private CompanyRepository companyRepository;
#Container
public MySQLContainer mysqlContainer = new MySQLContainer()
.withDatabaseName("foo")
.withUsername("foo")
.withPassword("secret");;
#Test
public void whenFindByIdExecuted_thenNullReturned()
throws Exception {
assertEquals(companyRepository.findById(1L), Optional.ofNullable(null));
}
#Test
public void whenFindAllExecuted_thenEmptyListReturned() {
assertEquals(companyRepository.findAll(), new ArrayList<>());
}
}
When I add #SpringBootTest, I need to set up all the context and have some Application load context issues?
The question is, can anyone demystify what #TestContainers annotation does? What is the best practice or correct to use it while testing the Repository?
The JUnit 5 extension provided by the #Testcontainers annotation scans for any containers declared with the #Container annotation, and then starts and stops the those containers for your tests. Containers as static fields will be shared with all tests, and containers as instance fields will be started and stopped for every test.
If you are using Spring Boot, the easiest way to setup testcontainers for your tests is probably to provide properties in application-test.yml. This will use the datasource JDBC URL to launch the testcontainers container. Refer to Testcontainers JDBC support for more information.
You can also test just the repository layer by using #DataJpaTest instead of #SpringBootTest:
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ActiveProfiles("test")
class CompanyRepositoryTest { }
Your application-test.yml file:
spring:
datasource:
url: jdbc:tc:mysql:8.0://hostname/databasename
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
In some cases you might also want to use the #TestPropertySource annotation instead:
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#TestPropertySource(
properties = {
"spring.datasource.url = jdbc:tc:mysql:8.0://hostname/test-database",
"spring.datasource.driver-class-name = org.testcontainers.jdbc.ContainerDatabaseDriver"
}
)
class CompanyRepositoryTest { }
Please note that the hostname and test-database are not actually used anywhere.
You said
When I add #SpringBootTest, I need to set up all the context and have
some Application load context issues?
If you'd like to try an alternative and Testcontainer is not mandatory you can do it differently.
You do not need to load everyting when using SpringBootTest annotation, you can specify which classes are needed such as
#SpringBootTest(classes = { TheService.class })
or use #Import annotation
and mock others such as
#MockBean
MyService service;
For database connection you can use annotation such as
#ActiveProfiles("my-profile-for-jpa-test")
#DataJpaTest
#EnableJpaAuditing
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
EDIT: I feel like this should be an comment but I wanted to address the SpringBootTest part of the question with proper formatting
Here is an example, how I configured Liquibase (a similar framework to Flyway) with MySql inside Spring:
#DataJpaTest
#TestPropertySource(properties = {"spring.jpa.hibernate.ddl-auto=validate"})
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration(initializers = { MySqlLiquibaseBaseIT.Initializer.class })
#Testcontainers
public class MySqlLiquibaseBaseIT {
#Container
public static MySQLContainer<?> mysql = new MySQLContainer<>(
DockerImageName
.parse(MySQLContainer.NAME)
.withTag("5.7.22"));
#Configuration
#EnableJpaRepositories
#EntityScan
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + mysql.getJdbcUrl(),
"spring.datasource.username=" + mysql.getUsername(),
"spring.datasource.password=" + mysql.getPassword(),
"spring.datasource.driver-class-name=" + mysql.getDriverClassName())
.applyTo(configurableApplicationContext.getEnvironment());
}
#Bean
public SpringLiquibase springLiquibase(DataSource dataSource) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDropFirst(true);
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yml");
return liquibase;
}
}
}
Full MySqlLiquibaseBaseIT.java
As per docs:
The test containers extension finds all fields that are annotated with
Container and calls their container lifecycle methods. Containers
declared as static fields will be shared between test methods. They
will be started only once before any test method is executed and
stopped after the last test method has executed. Containers declared
as instance fields will be started and stopped for every test method.
So in your case it will recreate a container for every test method, it's only responsible for starting and stopping the container. If you need some test data - that has to be done manually, as I see you have Flyway, that should do.
What "context issues" are you talking about?
Repositories are usually not tested separately, you can just test services which run repository methods instead of writing tests for both. If you want to test repos anyway - fill the database with some data in #Before.
If you have more questions please ask.

How to get Hibernate Envers with Spring integration testing?

Is there some way to get Hibernate Envers to run immediately on the call to save or update instead of going at the end of the transaction ?
I have a #Transactional annotation on my (effectively end-to-end test). Since the code doesn't contain test methods I'm unable to put "flush" or whatever in it, (short of editing non-test code, which is not the desire).
I'm not sure how to get Envers log working on the calls. I need it to work as I'm using the Envers audit tables to check old entries at runtime, and would like to create test cases for this functionality.
I have the following code for unit testing :
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration("/src/webapp")
#EnableWebMvc
#Transactional
#TestPropertySource(locations= {"classpath:/config/common.properties"}, properties = {"..."})
#ContextConfiguration(locations = {"..."})
public class Test {
#Autowired
private WebApplicationContext wac;
#Autowired
private UserDetailsService detailsService;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.build();
}
...
}

Resources