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

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.

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;
// ...
}

Different behaviour of injected fields in unit test and deployed version

I am new in lombok and spring and I have one issue which I am trying to understand. Firstly my code was design as below and then I modified it because I needed to have a setter.After that all tests passed. But after deployment I started to get null pointer exception for the service field.
So my question is why I am getting null pointer exception? I think after removing final keyword lombok does not create constructor for that field. But still I do not understand why it did not fail on unit tests if that is the reason? And what is the solution for it ?
This is the first version which works.
#Component
#RequiredArgsConstructor
public class MyClass{
private final MyServiceservice service;
private final MyOtherService otherservice;
}
This the version after update and where service field is null.
#Component
#RequiredArgsConstructor
public class MyClass{
#Setter
private MyServiceservice service;
private final MyOtherService otherservice;
}

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

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.

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