JPA 2.0 disable session cache for unit tests - java

I am writing unit test for my services e. g. :
#Test
#Rollback(value = true)
public void testMethod()
{
// insert test data
myService.Method(); // read/write from DB
// asserts go here
}
While application running, a new transaction is created every time method A entered. But during the unit test execution - when test testMethod entered. So method A doesn't create new one.
For proper testing I need to clear cache before every call to service inside test.I don't want to write Session.clear() before any call to service in each unit test. What is the best best practices here?

The EntityManager has a method clear() that will drop all persistence context:
Clear the persistence context, causing all managed entities to become detached. Changes made to entities that have not been flushed to the database will not be persisted.
If you call a query right after that method it will come directly from the database. Not from a cache.
If you want to run this before every test, consider using a JUnit #Rule by subclassing ExternalResource and running the method on every before() or after(). You can reuse that in al you database tests.

There are several way:
Evict Caches Manually
#Autowired private CacheManager cacheManager;
public void evictAllCaches(){
for(String name : cacheManager.getCacheNames()){
cacheManager.getCache(name).clear();
}
}
Turning Off Cache for Integration Test Profile
for Spring Boot: spring.cache.type=NONE
or
/** * Disabling cache for integration test */
#Bean public CacheManager cacheManager() {
return new NoOpCacheManager();
}
Use #DirtiesContext
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class CacheAffectedTest { ...
In this case Spring context re-created after every test and test's time in my measure tripling.
For developing Spring Boot Dev Tools turns caching off automatically during the development phase.
See Spring Cache and Integration Testing and A Quick Guide to #DirtiesContext

Related

How to save entities along with their related entities in spring boot integration testing?

I have an integration test on a endpoint of creating a user with its related entities. It turns out that the related entities were not persisted with the user entity.
However, it is working fine when running the normal spring boot application. Is it possible to achieve this during testing?
This is the log when running the integration test of the endpoint
And this is the log when calling from Postman to the normal application
as you can notice the roles are not inserted to the rel_mi_user__mi_user_role table during integration testing.
The setup source code for the integration test is shown below
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private MockMvc mockMvc;
#Autowired
private MiMiUserRepository miMiUserRepository;
private static Logger log = LoggerFactory.getLogger(MiMiUserResourceIT.class);
#Test
#Transactional
void testRegisterBackOfficeUser() throws Exception {
MiMiUserRegistrationDTO userRegistrationDTO = new MiMiUserRegistrationDTO();
userRegistrationDTO.setPassword("test12345");
userRegistrationDTO.setEmail("john#gmail.com");
userRegistrationDTO.setContactNo("0188991122");
userRegistrationDTO.setUserRoles(List.of("BACKOFFICE"));
MiMiUserProfileRegistrationDTO profile = new MiMiUserProfileRegistrationDTO();
profile.setSalutation("Mr");
profile.setFirstName("John");
profile.setLastName("Lee");
userRegistrationDTO.setProfile(profile);
mockMvc
.perform(
post("/v1/p/user/register/back-office")
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtil.convertObjectToJsonBytes(userRegistrationDTO))
)
.andExpect(status().isCreated());
MiUser u = miMiUserRepository.findOneWithMiUserRolesByEmailIgnoreCase(userRegistrationDTO.getEmail()).get();
assertThat(u.getUserStatus()).isEqualTo(UserStatus.NEW);
}
I guess it has to do with the fact that your whole test is annotated with #Transactional. When you annotate your test method with #Transactional, everything that happens during that test happens within one single transaction. As you might know, changes are only flushed to the database at the end of a transaction unless you call the flush method manually which is generally discouraged unless you know what you are doing because you interfere with your JPA provider's flushing optimization.
Only when the changes are flushed to the DB, auto-generated properties like an ID is set on your entity and DB constraints are evaluated. Yes, you're reading it right, you might even violate a DB constraint within your test without the test failing!
Here you can find more reasons why you should not annotate your tests with #Transactional.
If you need to save several entities in the arrange part of your test, you can autowire the Spring TransactionTemplate and create all your entities within a manual transaction. The same might apply to asserts where you load an entity from the DB and want to evaluate a lazy-loaded collection of said entity. As far as I can tell, neither of said scenarios apply to your test, just try omitting the #Transactional and your test should work.
Side note: if you write #DataJpaTests you always need to call the saveAndFlush method of the repository instead of the save method because #DataJpaTests are always transactional.

Spring testing DB rollback

I am #new to spring and faced some problem while running some tests. I have a few test-classes with the following code which should rollback my (in memory h2) database:
#Autowired
PlatformTransactionManager txm;
TransactionStatus txstatus;
#BeforeEach
public void setupDB() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txstatus = txm.getTransaction(def);
assumeTrue(txstatus.isNewTransaction());
txstatus.setRollbackOnly();
}
#AfterEach
public void rollback() {
txm.rollback(txstatus);
}
My problem is, that if one test class has finished, I get a JdbcSQLIntegrityConstraintViolationException (Unique index or primary key violation:..), because my Database is not rollbacked accordingly and Insert statements are executed again, because the database didnt get cleared. Does anyone has a tip how to fix that? Is there a way to rollback the inserts or not to do the inserts after they have been done ones?
You can just annotate your test class with
#Transactional
and Spring will handle everything (which means each test will run in its own transaction which will be rolled back after).
You can also use
#DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
but this is heavy because the whole Spring context must be recreated.
A simple solution is to use the DirtiesContext annotation. This annotation has several options. You can use the below line in your test class:
#DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
The context will be removed and recreated before the test class execution.

Should I use #Transactional or #Rollback for DAO test classes?

I have a dao layer for my database. Now, I am writing some integration tests for it. I wonder if #Transactional or #Rollback should be used in a test class, as they both revert the changes to the database. Which one would be a good practice and in what conditions?
I tried using both of them and they both work in my case. I have a #Before annotated method in my class.
#RunWith(SpringRunner.class)
#AutoConfigureTestDatabase(replace = NONE)
#DataJpaTest
// #Transactional or #Rollback?
public class TestDao {
#Autowired
private ConcreteDao concreteDao;
#Before
public void cleanUp(){ . . . }
#Test
public void testSaveAllEntries(){ . . . }
// and other tests
}
agree with #michael Don't make your tests #transactional at all (but your service layer)This means that all your service layer/persistence layer methods invoked through the tests would start their own transactions (you made them transactional, did you?) and flush the changes upon commit. So you are guaranteed to notice if something blows up on flush, and quite possible that after a while, a database full of junk test data
Running tests with databases usually will be done with integration tests. To keep it simple you could setup a h2 with the dialect you need. Prepare the database structures and run the service calls you need. assert the expecting results and mark the test method as dirties context (as annotation), or resetup the database with each test, otherwise saved results of a test could have impact of another test. In this way you can also test the transaction handling of your services.
Adding transactional to your tests will change your behaviour of your business logic. keep those out of your tests.

How can I persist entities during a SpringBootTest integration test

I am writing an integration test for a SpringBoot 2 RestController. I want to test 404 behaviour and creation of entities. However, when I try to create entities and persist them before or during a test, they are not persisted in the SpringBoot context. By that I mean they are visible in the test context (during debugging of the test) but not for the Controller (ie it does not find them and my tests fail). What am I doing wrong?
How can I persist entities and flush the context during a test so that code that is called during an integration test sees them? I don't want to use a #before annotation to populate a database because I want to do it in my #test methods.
Here is my code. Thanks
#RunWith(SpringRunner.class)
#Transactional
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class InvoiceControlllerIT extends GenericControllerIT {
#Autowired
EntityManager entityManager;
#Test
#Transactional
public void cascadesChildEntityAssociationOnCreate() throws IOException {
assertThat(invoicerRepository.count(), equalTo(0L));
assertThat(invoiceRepository.count(), equalTo(0L));
assertThat(invoiceeRepository.count(), equalTo(0L));
// create an invoicee
Invoicee savedInvoicee = invoiceeRepository.save(new Invoicee());
assertThat(invoiceeRepository.count(), equalTo(1L));
// create an invoicer
Invoicer savedInvoicer = invoicerRepository.save(new Invoicer());
assertThat(invoicerRepository.count(), equalTo(1L));
// THIS IS THE PROBLEM, FLUSHING DURING THE TEST DOES NOT EFFECT THE CONTROLLERS ABILITY TO SEE THE NEWLY CREATED ENTITIES
entityManager.flush();
// create input
InvoiceInputDto inputDto = InvoiceInputDto
.builder()
.invoicee(savedInvoicee.getId())
.invoicer(savedInvoicer.getId())
.name("test-name")
.build();
// make call
ResponseEntity<InvoiceDto> response = template.postForEntity(url("/invoices", TOKEN), inputDto, InvoiceDto.class);
assertThat(response.getStatusCode(), equalTo(HttpStatus.CREATED));
assertThat(response.getBody().getName(), equalTo(inputDto.getName()));
// check associations
assertThat(invoiceeRepository.findById(savedInvoicee.getId()).get().getInvoices(), hasSize(1));
}
}
According to the docs:
If your test is #Transactional, it rolls back the transaction at the
end of each test method by default. However, as using this arrangement
with either RANDOM_PORT or DEFINED_PORT implicitly provides a real
servlet environment, the HTTP client and server run in separate
threads and, thus, in separate transactions. Any transaction initiated
on the server does not roll back in this case
(source: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications)
Since the test transaction is separate from the HTTP server transaction, the controller won't see changes made from within the test method until the test transaction is actually committed. Conversely, you won't be able to roll back changes made as a result to the server call.
You will seriously make your life easier by providing a mock implementation for whatever service/repository your controller uses. Alternatively, you could use a tool like DBUnit to setup and tear down the database around each test case.
This worked for me:
#Inject
private EntityManagerFactory entityManagerFactory;
#BeforeEach
void setUp() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(someEntity());
entityManager.getTransaction().commit();
}

Spring unit test case is not rolling back insertion of a record

The following test cases are functionally working properly, but one of the test methods having to create a new article in the database doesn't rollback at the end of the test case execution.
I expect it to work that way. For a test case that update article actually rollbacks the update at the end of test case execution.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(value = "/applicationContext-test.xml")
#TransactionConfiguration(transactionManager = "txManager", defaultRollback = true)
#Transactional
public class PriceRepositoryTest {
#Resource(name ="repository")
private PriceRepository repository;
#Test
public void testGetAll() throws Exception {
Assert.assertEquals(8, repository.getAll().size());
}
#Test
#Rollback
public void shouldSaveNewArticle(){
Article article = new Article();
article.setName("Article");
article.setPrice(33);
repository.save(article);
Assert.assertEquals(9, repository.getAll().size());
}
#Test
#Rollback
public void shouldUpdateArticle(){
Article article = repository.getArticle(4);
article.setPrice(33);
repository.update(article);
Assert.assertEquals(String.valueOf(33.0), String.valueOf(repository.getArticle(4).getPrice()));
}
}
Is your DAO perhaps also marked with #Transactional? If it is, that's the problem: the transactionality on a different layer doesn't know about your local transaction configuration.
If repository.update(article) is #Transactional, it may or may not start a new transaction (depending on the value of the propagation attribute), but it will commit the transaction after execution, and there's nothing your test method can do to intercept that.
That's one of the reasons why transactions should be started on the service level, not the DAO level.
(If that's not the case, I humbly apologize)
I also encountered this issue and spent some hours trying to find the root cause. The issue was a variant of the problems described here. In my case, the application calls stored procedures through Spring, and one of these procedures contained a COMMIT statement.
Commits should always be controlled by the app, if there is a stray commit somewhere in a stored procedure, Spring can't control the transaction and tests won't roll back.
I just encountered this with my unit tests all set to rollback and my record was still showing up in the database after the test finished. The reason was there was that in the DAO a call to the entitymanager to flush() was in the method - which forced the transaction to commit.
em.persist(jpaServer);
em.flush(); //commits the record no matter what spring is setup to do
taking the flush out and confirmed no record. Tested the same code with the test annotated to #Rollback(false) and confirmed the record (proving the flush was not needed)
Ben
www.nimbits.com

Categories