How can I force a transaction commit in Spring Boot (with Spring Data) while running a method and not after the method ?
I've read here that it should be possible with #Transactional(propagation = Propagation.REQUIRES_NEW) in another class but doesn't work for me.
Any hints? I'm using Spring Boot v1.5.2.RELEASE.
#RunWith(SpringRunner.class)
#SpringBootTest
public class CommitTest {
#Autowired
TestRepo repo;
#Transactional
#Commit
#Test
public void testCommit() {
repo.createPerson();
System.out.println("I want a commit here!");
// ...
System.out.println("Something after the commit...");
}
}
#Repository
public class TestRepo {
#Autowired
private PersonRepository personRepo;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void createPerson() {
personRepo.save(new Person("test"));
}
}
Use the helper class org.springframework.test.context.transaction.TestTransaction (since Spring 4.1).
Tests are rolled back per default. To really commit one needs to do
// do something before the commit
TestTransaction.flagForCommit(); // need this, otherwise the next line does a rollback
TestTransaction.end();
TestTransaction.start();
// do something in new transaction
An approach would be to inject the TransactionTemplate in the test class, remove the #Transactional and #Commit and modify the test method to something like:
...
public class CommitTest {
#Autowired
TestRepo repo;
#Autowired
TransactionTemplate txTemplate;
#Test
public void testCommit() {
txTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
repo.createPerson();
// ...
}
});
// ...
System.out.println("Something after the commit...");
}
Or
new TransactionCallback<Person>() {
#Override
public Person doInTransaction(TransactionStatus status) {
// ...
return person
}
// ...
});
instead of the TransactionCallbackWithoutResult callback impl if you plan to add assertions to the person object that was just persisted.
Solutions with lambdas.
import java.lang.Runnable;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.support.TransactionTemplate;
#Autowired
TestRepo repo;
#Autowired
TransactionTemplate txTemplate;
private <T> T doInTransaction(Supplier<T> operation) {
return txTemplate.execute(status -> operation.get());
}
private void doInTransaction(Runnable operation) {
txTemplate.execute(status -> {
operation.run();
return null;
});
}
use as
Person saved = doInTransaction(() -> repo.save(buildPerson(...)));
doInTransaction(() -> repo.delete(person));
Related
I have a service that has a DataProvider which I want to mock.
Problem: the service uses the data provider in #PostConstruct. But when I use #MockBean, the mocked values are not jet present in #PostConstruct.
What could I do?
#Service
public class MyService {
private List<Object> data;
#Autowired
private DataProvider dataProvider;
#PostConstruct
public void initData() {
data = dataProvider.getData();
}
public void run() {
System.out.println(data); //always null in tests
}
}
#SpringBootTest
public class Test {
#MockBean
private DataProvider dataProvider;
#Test
public void test() {
when(dataProvider.getData()).thenReturn(mockedObjects);
//dataProvider.init(); //this fixes it, but feels wrong
service.run();
}
}
IMHO unit testing MyService would be a better solution for this particular scenario (and I wouldn't feel wrong about calling initService manually in that case), but if you insist...
You could simply override the DataProvider bean definition for this particular test and mock it beforehand, sth like:
#SpringBootTest(classes = {MyApplication.class, Test.TestContext.class})
public class Test {
#Test
public void test() {
service.run();
}
#Configuration
static class TestContext {
#Primary
public DataProvider dataProvider() {
var result = Mockito.mock(DataProvider.class);
when(result.getData()).thenReturn(mockedObjects);
return result;
}
}
}
You might need to set spring.main.allow-bean-definition-overriding to true for the above to work.
Is it possible to use dependency injection with unit tests using Spring Boot? For integration testing #SpringBootTest start the whole application context and container services. But is it possible to enable dependency injection functionality at unit test granularity?
Here's the example code
#ExtendWith(SpringExtension.class)
public class MyServiceTest {
#MockBean
private MyRepository repo;
#Autowired
private MyService service; // <-- this is null
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
#Service
public class MyService {
private final MyRepository repo; // <-- this is null
public MyService(MyRepository repo) {
this.repo = repo;
}
public List<String> getData() {
return repo.findAll().stream()
.map(MyEntity::getData)
.collect(Collectors.toList());
}
}
Or should I just manage the SUT (service class) as POJO and manually inject the mocked dependencies? I want to keep tests fast but minimize boilerplate code.
As #M.Deinum mentioned in the comments, unit tests shouldn't use dependency injection. Mock MyRepository and inject MyService using Mockito (and Junit5):
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#InjectMocks
private MyService service;
#Mock
private MyRepository repo;
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
If you want to test the repository, use #DataJpaTest. From the docs:
Using this annotation will disable full auto-configuration and instead
apply only configuration relevant to JPA tests.
#DataJpaTest
public class MyRepositorTest {
#Autowired
// This is injected by #DataJpaTest as in-memory database
private MyRepository repo;
#Test
void testCount() {
repo.save(new MyEntity("hello"));
repo.save(new MyEntity("world"));
assertEquals(2, repo.count());
}
}
In conclusion, the suggested approach is to test the service layer mocking the repository layer with Mockito (or similar library) and to test the repository layer with #DataJpaTest.
You have not added the #Autowired in service for MyRepository
Service Class
#Service
public class MyService {
private final MyRepository repo; // <-- this is null
#Autowired
public MyService(MyRepository repo) {
this.repo = repo;
}
public List<String> getData() {
return repo.findAll().stream()
.map(MyEntity::getData)
.collect(Collectors.toList());
}
}
Service Test Class
#ExtendWith(MockitoExtension.class)
public class MyServiceTest {
#Mock
private MyRepository repo;
#InjectMocks
private MyService service;
#Test
void getData() {
MyEntity e1 = new MyEntity("hello");
MyEntity e2 = new MyEntity("world");
Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
List<String> data = service.getData();
assertEquals(2, data.size());
}
}
I am having an issue with stubbing my repository. I was suggested to just create another application.properties (which I have not done) and to use an in-memory database like H2. I was wondering though if I can just stub the call so when myDataService.findById(id) is called instead of it attempting to get that from the database just a mocked object can be returned?
I am new to writing mocks for my unit tests and spring boot in general so maybe I am missing something. Code below (tried to simplify and made names generic for posting here).
My test class
public class MyServiceImplTest
{
private MyDataService myDataService;
private NyService myService;
private MyRepository myRepository;
#Before
public void setUp() {
myDataService = Mockito.mock(MyDataServiceImpl.class);
myService = new MyServiceImpl(myDataService);
}
#Test
public void getById_ValidId() {
doReturn(MyMockData.getMyObject()).when(myDataService).findById("1");
when(myService.getById("1")).thenReturn(MyMockData.getMyObject());
MyObject myObject = myService.getById("1");
//Whatever asserts need to be done on the object myObject
}
}
Class used for making the service call to the data layer
#Service
public class MyServiceImpl implements MyService {
MyDataService myDataService;
#Autowired
public MyServiceImpl(MyDataService myDataService) {
this.myDataService = myDataService;
}
#Override
public MyObject getById(String id) {
if(id == null || id == "") {
throw new InvalidRequestException("Invalid Identifier");
}
MyObject myObj;
try {
myObj = myDataService.findById(id);
}catch(Exception ex) {
throw new SystemException("Internal Server Error");
}
return myObj;
}
}
This is where I am having the issue in my test. When the findById() method is called, the variable repository is null so when trying to do repository.findOne(id) it throws an exceptionn. This is what I am attempting to mock, but the repository is giving me issues.
#Repository
#Qualifier("MyRepo")
public class MyDataServiceImpl {
#PersistenceContext
private EntityManager em;
private MyRepository repository;
#Autowired
public MyDataServiceImpl(MyRepository repository) {
super(repository);
this.repository = repository;
}
public MyObject findById(String id) {
P persitentObject = repository.findOne(id);
//Calls to map what persitentObject holds to MyObject and returns a MyObject
}
}
Code for MyRepository here just to show it's an empty interface that extends CrudRepository
public interface MyRepository extends CrudRepository<MyObjectPO, String>, JpaSpecificationExecutor<MyObjectPO> {
}
Let me begin by saying you are on the right track by using Constructor Injection and not Field Injection(which makes writing tests with mocks much simpler).
public class MyServiceImplTest
{
private MyDataService myDataService;
private NyService myService;
#Mock
private MyRepository myRepository;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this); // this is needed for inititalizytion of mocks, if you use #Mock
myDataService = new MyDataServiceImpl(myRepository);
myService = new MyServiceImpl(myDataService);
}
#Test
public void getById_ValidId() {
doReturn(someMockData).when(myRepository).findOne("1");
MyObject myObject = myService.getById("1");
//Whatever asserts need to be done on the object myObject
}
}
The call goes all the way from your service --> dataService. But only your repository calls are mocked.
This way you can control and test all the other parts of your classes(both service and dataService) and mock only repository calls.
I'am trying to do a simple Integration test using Spring Boot Test in order to test the e2e use case. My test does not work because I'am not able to make the repository saving data, I think I have a problem with spring contexts ...
This is my Entity:
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Person {
#Id
private int id;
private String name;
}
This is the Person repository:
#Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {
}
The Person service:
#Service
public class PersonService {
#Autowired
private PersonRepository repository;
public Person createPerson(int id,String name) {
return repository.save(new Person(id, name));
}
public List<Person> getPersons() {
return repository.findAll();
}
}
The Person Controller:
#RequestMapping
#RestController
public class PersonController {
#Autowired
private PersonService personService;
#RequestMapping("/persons")
public List<Person> getPersons() {
return personService.getPersons();
}
}
The main Application class:
#SpringBootApplication
public class BootIntegrationTestApplication {
public static void main(String[] args) {
SpringApplication.run(BootIntegrationTestApplication.class, args);
}
}
The application.properties file:
spring.datasource.url= jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
And the Test:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BootIntegrationTestApplicationTests {
#Autowired
private PersonService personService;
#Autowired
private TestRestTemplate restTemplate;
#Test
#Transactional
public void contextLoads() {
Person person = personService.createPerson(1, "person1");
Assert.assertNotNull(person);
ResponseEntity<Person[]> persons = restTemplate.getForEntity("/persons", Person[].class);
}
}
The test does not work, because the service is not saving the Person entity ....
Thanks in advance
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class SmokeTest {
#Autowired
UserController userController;
#Autowired
UserDao userDAO;
#Rollback(false) // This is key to avoid rollback.
#Test
public void contextLoads() throws Exception {
System.out.println("Hiren");
System.out.println("started");
userDAO.save(new User("tyx", "x#x.com"));
}
}
Refer #Rollback(false) is key to avoid rollback.
Thanks to M. Deinum, I think I get the point,
So the best is to separate the logic of the test into two tests, the first will testing just the service (so this one could be transactional) and the second the controller:
Test 1:
#Test
#Transactional
public void testServiceSaveAndRead() {
personService.createPerson(1, "person1");
Assert.assertTrue(personService.getPersons().size() == 1);
}
Test 2:
#MockBean
private PersonService personService;
#Before
public void setUp() {
//mock the service
given(personService.getPersons())
.willReturn(Collections.singletonList(new Person(1, "p1")));
}
#Test
public void testController() {
ResponseEntity<Person[]> persons = restTemplate.getForEntity("/persons", Person[].class);
Assert.assertTrue(persons.getBody()!=null && persons.getBody().length == 1);
}
Spring for saving entity requires transaction. But until transaction has been commited changes not be visible from another transaction.
Simplest way is call controller after commit transaction
#Test
#Transactional
public void contextLoads() {
Person person = personService.createPerson(1, "person1");
Assert.assertNotNull(person);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
ResponseEntity<Person[]> persons = restTemplate.getForEntity("/persons", Person[].class);
}
});
}
For each #Test function that makes a DB transaction, if you want to permanently persist the changes, then you can use #Rollback(false)
#Rollback(false)
#Test
public void createPerson() throws Exception {
int databaseSizeBeforeCreate = personRepository.findAll().size();
// Create the Person
restPersonMockMvc.perform(post("/api/people")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(person)))
.andExpect(status().isCreated());
// Validate the Person in the database
List<Person> personList = personRepository.findAll();
assertThat(personList).hasSize(databaseSizeBeforeCreate + 1);
Person testPerson = personList.get(personList.size() - 1);
assertThat(testPerson.getFirstName()).isEqualTo(DEFAULT_FIRST_NAME);
assertThat(testPerson.getLastName()).isEqualTo(DEFAULT_LAST_NAME);
assertThat(testPerson.getAge()).isEqualTo(DEFAULT_AGE);
assertThat(testPerson.getCity()).isEqualTo(DEFAULT_CITY);
}
I tested it with a SpringBoot project generated by jHipster:
SpringBoot: 1.5.4
jUnit 4.12
Spring 4.3.9
Pay your attention to the order in which the tests are executed, the tests with the #Commit or #Rollback(false) annotation must be executed first: https://www.baeldung.com/junit-5-test-order
Do not use #Rollback(false). Unit Test should not generate data.
JPA FlushMode is AUTO (default - flush INSERT/UPDATE/DELETE SQL when query occurs) / COMMIT.
Just query the working entity for forcing FLUSH, or using EntityManager to force flush
#Test
public void testCreate(){
InvoiceRange range = service.createInvoiceRange(1, InvoiceRangeCreate.builder()
.form("01GTKT0/010")
.serial("NV/18E")
.effectiveDate(LocalDate.now())
.rangeFrom(1L)
.rangeTo(1000L)
.build(), new byte[] {1,2,3,4,5});
service.findByCriteria(1, "01GTKT0/010", "NV/18E"); // force flush
// em.flush(); // another way is using entityManager for force flush
}
I've a particular flow calls in my Spring app.
I've a repository, and I call the method sell() as first call.
#Service
#Transactional
public class ServiceRepositoryImpl implements ServiceRepository {
#Inject
private SellRepository sellRepository;
#Override
public long sell(long id)
....
....
....
Sell sell = new Sell();
...
sellRepository.save(sell);
}
and my SellService:
#Service
public class SellRepositoryImpl implements SellRepository {
#PersistenceContext
private EntityManager entityManager;
#Inject
SellHelperRepository sellHelperRepository;
#Inject
private TransactionTemplate transactionTemplate;
#Override
public <S extends Sell> S save(S sell) throws Exception {
transactionTemplate.execute(new TransactionCallback<S>() {
#Override
public S doInTransaction(TransactionStatus status) {
try {
...
entityManager.persist(sell);
} catch (Throwable e) {
log.error("", e);
status.setRollbackOnly();
}
return vendita;
}
});
sellHelperRepository.createTickets(sell.getId());
return vendita;
}
this is my sellHelperRepository:
#Service
#Transactional
public class SellHelperRepositoryImpl implements SellHelperRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
#Async
public void createTickets(long sellID) {
Sell sell = entityManager.find(Sell.class, sellID);
try{
...
Ticket t = new Ticket();
ticketService.save(t);
}catch(Throwable e){
sell.setStatus("CANCELED");
}
}
and in the end my ticketService:
#Service
#Transactional
public class TicketRepositoryImpl implements TicketRepository {
#PersistenceContext
protected EntityManager entityManager;
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Biglietto save(Ticket ticket) throws Exception {
entityManager.persist(ticket);
}
The problem is that in this chain of calls when I'm on SellRepositoryImpl, the object sell is not persisted asap I'm out of the transactiontemplate but it should be! So when I enter in SellHelperRepositoryImpl and I try to search the sell it doesn't find it!
This particular structure is needed because these methods are used also from others repository.
I created many junit tests case to check if all works fine; for the rest of tests all works fine, a part for this particular chain of calls.
I think I'm missing something....
Thanks
the problem with your code is that you are using the #Async on the createTickets method , which forces Spring to execute it in the different thread with the "fresh" transaction, so you need to flush/commit first transaction which was opened in the SellRepositoryImpl class.
So you can go in three ways
Remove the #Async from the createTickets method
Change the Transaction Isolation Level from DEFAULT to READ_UNCOMMITTED in SellHelperRepositoryImpl
#Service
#Transactional(isolation=Isolation.READ_UNCOMMITTED)
public class SellHelperRepositoryImpl implements SellHelperRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
#Async
public void createTickets(long sellID) {
Sell sell = entityManager.find(Sell.class, sellID);
try{
...
Ticket t = new Ticket();
ticketService.save(t);
}catch(Throwable e){
sell.setStatus("CANCELED");
}
}
Flush the manually managed transaction in save(S sell), see following code snippet.
#Override
public <S extends Sell> S save(S sell) throws Exception {
transactionTemplate.execute(new TransactionCallback<S>() {
#Override
public S doInTransaction(TransactionStatus status) {
try {
...
entityManager.persist(sell);
entityManager.getTransaction().commit();
status.flush();
} catch (Throwable e) {
log.error("", e);
status.setRollbackOnly();
}
return vendita;
}
});
sellHelperRepository.createTickets(sell.getId());
return vendita;
}