junit test cases for SpringBoot application which uses non JPA Repository object - spring

I am writing an springboot component which is simply responsible for auditing login operation.
Since component is only responsible to write into database and there will be no retrieve(select) operation on table. I am simply using #Repository(org.springframework.data.repository.Repository) package and implemented method with insert into statement to write in database.
#Repository
public interface AuditRepository extends Repository<UserAudit,String> {
#Modifying
#Query(value = "insert into user_audit(user_id,datetime,function_code,ip_address) values (:user_id,:datetime,:function_code,:ip_address)",nativeQuery = true)
#Transactional
public void recordUserAudit(#Param("user_id")String user_id, #Param("datetime") Timestamp datetime, #Param("function_code") int function_code, #Param("ip_address") String ipAddress);
}
Execution of this method on http request does works out.
I'd want to write junit tests on H2 database which verifies record is inserted correctly. for which I am using test profile. inserting record on test method with H2 dependency also seem to work - however I currently don't see a way to verify existence of record.
Any suggestions How to achieve it?
Ps. I understand this is possible with either #JpaRepository(org.springframework.data.jpa.repository.JpaRepository) or #CrudRepository but I'd prefer not to use them as using Repository and distinct method will make application light weight.

you can #Mock your #Repository object and with org.mockito.Mockito.verify and will be able to verify if the Sql written above does gets executed upon calling. (authentication request)
ie. mock AuditRepository and verify object.
verify(auditRepository).recordUserAudit(user_id,datetime, function_code, ipAddress);

Related

Integration Flow Test Spring Transaction

I'm currently writing a Spock integration test for my Spring application.
I'd like to use #Stepwise in order to perform a test which interacts with the database and then have the next test build on top of the data left behind from the first test.
Unfortunately it seems that a new transaction is started for every test method, thus clearing the data I need to build upon. Rollback(false) does not prevent this behaviour, since the whole transaction is discarded AFAIK.
Here's an example, the MyUserService interacts with a #Repository-interface.
#Stepwise
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
class MyServiceImplIntegrationFlowSpec extends Specification {
#Autowired
#Subject
MyUserService myUserService
#Shared
String userId
void "create user"() {
when:
userId = myUserService.createUser()
then:
userId
}
void "change user permission"() {
when:
myUserService.changePermission(userId, "read")
then:
myUserService.fetchPermission() == "read"
}
}
How can I reuse the data which was created by the previous test method, as is commonly done with #Stepwise, in conjunction with database operations?
The Spring Test framework rolls back the data of each test method by default. You can change this default behaviour by adding the #Commit annotation to each of your test methods where you want to keep the changes in the database. If the whole test suite should commit data to the database I think you can put the #Commit annotation also on class level.
See the reference https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testing-tx
It says:
One common issue in tests that access a real database is their effect
on the state of the persistence store. Even when you use a development
database, changes to the state may affect future tests. Also, many
operations — such as inserting or modifying persistent data — cannot
be performed (or verified) outside of a transaction.
and continues describing with
The TestContext framework addresses this issue. By default, the
framework creates and rolls back a transaction for each test. You can
write code that can assume the existence of a transaction. If you call
transactionally proxied objects in your tests, they behave correctly,
according to their configured transactional semantics. In addition, if
a test method deletes the contents of selected tables while running
within the transaction managed for the test, the transaction rolls
back by default, and the database returns to its state prior to
execution of the test. Transactional support is provided to a test by
using a PlatformTransactionManager bean defined in the test’s
application context.
If you want a transaction to commit (unusual, but occasionally useful
when you want a particular test to populate or modify the database),
you can tell the TestContext framework to cause the transaction to
commit instead of roll back by using the #Commit annotation.
Your test case could look like
#Stepwise
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#Commit // if you want all test methods to commit to the database
class MyServiceImplIntegrationFlowSpec extends Specification {
#Autowired
#Subject
MyUserService myUserService
#Shared
String userId
#Commit // if a single test needs to commit to the database
void "create user"() {
when:
userId = myUserService.createUser()
then:
userId
}
void "change user permission"() {
when:
myUserService.changePermission(userId, "read")
then:
myUserService.fetchPermission() == "read"
}
}

Repository Test runs in isolation but fails overall (mvn test)

When I run test cases for a UserRepository, it runs in isolation (say, mvn test -Dtest=UserRepository.class).
But when I run mvn test, it fails.
I get a java.lang.NullPointerException
My test class is as:
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserRepositoryTest {
#Autowired
private EntityManager entityManager;
#Autowired
private UserRepository userRepository;
// This validates the Hibernate Constraints
private Validator validator;
A sample test case is:
#Test
public void repo_should_create_a_user() {
User u = new User();
u.setUserName("firstUser");
u.setRole(User.Role.USER);
entityManager.persist(u);
User user = userRepository.findAll().get(0);
Assert.assertThat( user, instanceOf(User.class) );
assertEquals("firstUser", user.getUserName());
}
The User entity has regular properties like email and password, and so forth. Also, it contains auditing features like created_by and modified_by which makes use of the security context to fetch the current user.
Any help would be appreciated. Thanks.
One of the possibilities is that when different tests are run, the later tests get the database in a dirty state after the first test, so if the UserRepositoryTest runs after other tests, they might leave some garbage in the database.
In general, the best is to make sure that there is no 'garbage' data in the database.
One way is to use #Transactional annotation on tests so that if everything is integrated correctly, the data that was changed during the tests will be automatically removed from db after the test because Spring will rollback the transaction.
That will guarantee that the next step won't get that garbage.
As it turns out I use using database auditing properties in my User class, and created_by and modified_by properties in the User class makes use of SecurityContextHolder.getContext().getAuthentication() to fetch the current user.
I added the #WithMockUser to the test method and it started to work. I don't exactly know how the independent class only or method only test cases worked, and overall test failed. But adding this annotation apparently makes everyone happy.

Spring native query executed within a transaction taking outdated value

I'm using Spring Boot (1.4.4.REALEASE) with Spring Data in order to manage a MySql Database. I've got the following case:
We update one revision performed in one equipment using the RevisionService.
RevisionService saves the revision and calls the EquipmentService to update the equipment status.
The updateEquipmentStatus does a call to a Db stored procedure in order to evaluate the equipment with its revisions altogether and update the field.
I've tried some options but don't achieve to get the updated status for the equipment. The updateEquipmentStatus method keeps writing the previous status for the equipment (not considering the current revision being stored in the transaction). The code is written this way:
RevisionService
#Service
public class RevisionService{
#org.springframework.transaction.annotation.Transactional
public Long saveRevision(Revision rev){
//save the revision using JPA-Hibernate
repo.save(rev);
equipmentService.updateEquipmentStatus(idEquipment);
}
}
EquipmentService
#Service
public class EquipmentService{
#org.springframework.transaction.annotation.Transactional
public Long updateEquipmentStatus(Long idEquipment){
repo.updateEquipmentStatus(idEquipment);
}
}
EquipmentRepo
#Repository
public interface EquipmentRepo extends CrudRepository<Equipment, Long> {
#Modifying
#Procedure(name = "pupdate_equipment_status")
void updateEquipmentStatus(#Param("id_param") Long idEquipment);
}
As far as I understand, as both methods are annotated with Spring's transactional, the updateEquipmentStatus method should be executed in the scope of the current transaction. I've also tried with different options for the #Transactional annotation from updateEquipmentStatus, such as #Transactional(isolation=Isolation.READ_UNCOMMITTED) (which shouldn't be required, because I'm using the same transaction) and #Transactional(propagation=Propagation.REQUIRES_NEW), but keeps not considering the current status. That's how my stored procedure is saved into the MySql DB:
CREATE DEFINER=`root`#`localhost` PROCEDURE `pupdate_equipment_status`(IN `id_param` INT)
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
COMMENT ''
BEGIN
/*Performs the update considering tequipment and trevision*/
/*to calculate the equipment status, no transaction is managed here*/
END
I also want to clarify that if I execute some modification in the equipment itself (which affects only tequipment), the status is being properly updated. InnoDb is the engine being used for all the tables.
UPDATE
Just changed the repo method to use a nativeQuery instead and the same problem keeps happening, so the Db procedure being involved should be discarded:
#Modifying
#Query(nativeQuery = true, value= "update tequipment set equipment_status = (CASE WHEN (...))")
void updateEquipmentStatus(#Param("id_param") Long idEquipment);
UPDATE2
Having done more tests and added a log with TransactionSynchronizationManager.getCurrentTransactionName() in the methods, that's the concrete issue:
Changes done in the equipment service are properly picked by the updating function (When something in tequipment changes, the status in tequipment is calculated properly).
Changes done in the revision service (trevision) result in an outdated value in tequipment (it doesn't matter if Spring does it in a different transaction using REQUIRES_NEW or not). Spring seems to create a new transaction properly when using REQUIRES_NEW in establishEquipmentStatus, because the current transaction name changes, but the native query doesn't have the latest values (because of the transaction before not being commited?). Also tried removing #Transactional from establishEquipmentStatus so the same transaction is used, but the issue keeps happening.
I would like to highlight that the query used to update equipment status has a case expression with multiple subqueries using trevision.
Adding the following code fixes it (programatically flushing the transaction state to the Database):
#Service
public class EquipmentService{
#PersistenceContext
private EntityManager entityManager;
#org.springframework.transaction.annotation.Transactional
public Long updateEquipmentStatus(Long idEquipment){
entityManager.flush();
repo.updateEquipmentStatus(idEquipment);
}
}
Still it would be great to find a declarative way to do it..
Changing to read uncommitted is the right idea but you'd also need to flush the entitymanager before your stored procedure is called. See this thread:
How to make the queries in a stored procedure aware of the Spring Transaction?
Personally I'd do it all in Spring unless you are absolutely forced to use a stored procedure.

How can I test that a JPA save actually saves data?

I am using JPA with Spring and saving an entity in a test. In the process of writing a test to validate that an entity's relationship with another entity is correctly set up, I have come across a problem that I come across frequently. I have a test method (set to rollback) that:
Creates entity
Saves entity
Flushes
Retrieves entity
Validates entity
The problem is that when I look at the Hibernate logs, I only see a single insert to the database where I'd expect to see an insert and then a select.
I know this is because Hibernate's trying to save me some time and knows that it's got the entity with the ID I'm trying to retrieve but that bypasses an important step: I want to make sure that the entity actually made it to the database and looks like what I thought it should. What's the best way to deal with this so I can test that the entity is actually in the database?
Note: I assume this involves somehow detaching the entity or telling Hibernate to clear its cache but I'm not sure how to do that when all I have access to is a JpaRepository object.
Some code:
public interface UserRepository extends JpaRepository<User, Long> {
//...
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = JpaConfig.class, // JpaConfig just loads our config stuff
loader = AnnotationConfigContextLoader.class)
#TransactionConfiguration(defaultRollback = true)
public class UserRepositoryTest {
#Test
#Transactional
public void testRoles() {
User user = new User("name", "email#email.com");
// eventually more here to test entity-to-entity relationship
User savedUser = userRepository.save(user);
userRepository.flush();
savedUser = userRepository.findOne(savedUser.getId());
Assert.assertNotNull(savedUser);
// more validation here
}
}
You basically want to test Hibernate's functionality instead of your own code. My first suggestion: don't do it! It is already tested and validated many times.
If you really want to test it, there are a couple of options:
Execute a query (rather than a get. The query will get executed (you should see it in the log) and the result interpreted. The object you get back would still be the same object you saved, since that is in the session.
You can evict the object from the session and then get it again. If you use SessionFactory.getCurrentSession(), you'll get the same season that the repository is using. With that you can evict the object.
You have two strategies:
issue a native SQL query therefor bypassing any JPA cache.
ensure the persistence context is cleared before reloading.
For (1) you can change your tests to extend the following Spring class which, in addition to automatically beginning/rolling back a transaction at the start/end of each test, will give you access to a Spring JdbcTemplate you can use to issue the native SQL.
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.html
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/jdbc/core/simple/SimpleJdbcTemplate.html
For (2) you can clear the persistence context by doing the following (where the EntityManagerFactory is injected into your test:
EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory).clear();
See the following base test class which I normally use and demonstrates the above and also allows for populating the database with known data before each test (via DBUnit).
https://github.com/alanhay/spring-data-jpa-bootstrap/blob/master/src/test/java/uk/co/certait/spring/data/repository/AbstractBaseDatabaseTest.java
(In fact in the above I am actually creating a new JdbcTemplate by injecting a datasource. Can't remember why...)

Spring Database Integration Test, when to flush or?

I am fairly new to spring, and doing some integration tests.
Using Hibernate, MySql and Spring data JPA.
I am using transaction support and everything gets rolled back at the end of each test.
For example:
#Test (expected=DataIntegrityViolationException.class)
public void findAndDelete() {
UUID uuid = UUID.fromString(TESTID);
User user= iUserService.findOne(uuid);
iUserService.delete(cashBox);
iUserService.flush();
assertNull(iUserService.findOne(uuid));
}
In the above code, I call the iUserService.flush(), so that the sql gets sent to the DB, and an expected DataIntegrityViolationException occurs because there is a foreign key from User to another table (Cascade is not allowed, None). All good so far.
Now, if I remove the iUserService.flush()
then the expected exception does not occur because the sql does not get sent to the DB.
I tried adding the flush() into a teardown #After method, but that didn't work as the test does not see the exception outside of the test method.
Is there any way to avoid calling the flush within the test methods?
It would be preferable if the developers on my team did not have to use the flush method at all in their testing code
Edit:
I tried adding the following
#Before
public void before() {
Session session = entityManagerFactory.createEntityManager().unwrap(Session.class);
session.setFlushMode(FlushMode.ALWAYS);
}
but it does seem to flush the sqls, before each query.
In my humble opinion, it's better than the developers of your team know what they are doing.
It includes the things that are configured by default and the consequences of that.
Please, take a look to why you need avoid false positives when testing ORM code

Resources