Reputation: 5843
I would like to write tests about revision. In the console I see the update call of Hibernate, BUT no insertions into AUD-Table.
Test-Method:
@DataJpaTest
class JPAHistoryTest {
@Test
public void test() {
def entity = // create Entity
def entity2 = // create same Entity
entity2.setPrice(entity2.getPrice() + 10)
entity2.setLastUpdate(entity2.lastUpdate.plusSeconds(10))
service.save(entity)
service.save(entity2)
repository.flush() // Hibernate updates changes
assert repository.findRevisions(entity.id).content.empty == false // FAIL!
}
}
My Entity looks like:
@Entity
@Audited
class Entity {
@Id @GeneratedValue Long id
@Column(nullable = false) BigDecimal price
}
Thank you very much.
Upvotes: 9
Views: 7654
Reputation: 26878
If you don't want to change the transactional behaviour of the whole test class like in the answer of Michael Hegner, you can do the following:
@RunWith(SpringRunner)
@DataJpaTest
class JPAHistoryTest {
@Autowired
private MyRepository repository;
@Autowired
private PlatformTransactionManager platformTransactionManager;
@After
void after() {
repository.deleteAll()
}
@Test
public void testTwoInsert() {
TransactionTemplate tx = new TransactionTemplate(platformTransactionManager);
tx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
def entity1 = // ...
def entity2 = // ...
tx.execute(status -> repository.save(entity1))
tx.execute(status -> repository.save(entity2))
assert repository.findRevisions(entity1.id).content.size() == 1
assert repository.findRevisions(entity2.id).content.size() == 1
}
}
So create a TransactionTemplate
with PROPAGATION_NOT_SUPPORTED
and do the saving using that transaction template. This also triggers the auditing information being saved.
Upvotes: 2
Reputation: 3863
I found same problem as you and tried @michael-hegner answer, but I used TestEntityManager
class so I can't get EntityManager
when there is no transaction (propagation
set to NOT_SUPPORTED
).
In my case the solution was to manually commit transaction from TestEntityManager
, to first save entity and changes and next query for revisions.
Here's a test class:
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager testEntityManager;
@Test
public void shouldBeAudited() {
User user = getTestUser();
testEntityManager.persistAndFlush(user);
user.setPassword("tset");
testEntityManager.merge(user);
// This line is critical here to commit transaction and trigger audit logs
testEntityManager.getEntityManager().getTransaction().commit();
// With propagation = NOT_SUPPORTED this doesn't work: testEntityManager.getEntityManager()
AuditReader auditReader = AuditReaderFactory.get(testEntityManager.getEntityManager());
List revisions = auditReader.createQuery()
.forRevisionsOfEntity(User.class, true)
.add(AuditEntity.id().eq(user.getId()))
.getResultList();
assertEquals(1, revisions.size());
}
private User getTestUser() {
User user = new User();
user.setUsername("test");
user.setEmail("test");
user.setPassword("test");
return user;
}
}
It could be mandatory to manually remove user after Test because transaction was commited and in other tests it may causes some problems.
Upvotes: 5
Reputation: 5843
As I found out I keep the @DataJpaTest
and add @Transactional(propagation = NOT_SUPPORTED)
to make sure, the test methods will not start a transaction. Because if they would run in a transaction, then the envers history entries will be written, when the test close the transaction.
@RunWith(SpringRunner)
@DataJpaTest
@Transactional(propagation = NOT_SUPPORTED)
class JPAHistoryTest {
@After
void after() {
repository.deleteAll()
}
@Test
public void testTwoInsert() {
def entity1 = // ...
def entity2 = // ...
sut.save(entity1 )
sut.save(entity2 )
assert repository.findRevisions(entity1.id).content.size() == 1
assert repository.findRevisions(entity2.id).content.size() == 1
}
}
Upvotes: 11
Reputation: 21153
I honestly don't follow your naming convention here. Typically a service method call is wrapped by some transactional context in your production code.
@Service
public class Service1 {
@Transactional
public void save(Object object) {
}
}
@Service
public class Service2 {
@Transactional
public void save(Object object) {
}
}
For cases where you need to bind two service method calls within a single transactional context you'd wrap those two separate services by another service invocation.
@Service
public class ServiceWrapper {
@Autowired
private Service1 service1;
@Autowired
private Service2 service2;
@Transactional
public void save(Object object1, Object object2) {
service1.save( object1 );
service2.save( object2 );
}
}
It's important to point out that this ServiceWrapper
doesn't necessarily have to exist in your production code. This bean could be something you create solely for your test. The point is this bean is constructed in a very Spring-like way to satisfy the transaction boundary you need.
Now inside your test you can use the repository to lookup the entity because the transaction inside the wrapper commits the data, allowing the lookup to happen into the Envers audit tables as expected.
@Test
public void testSomething() {
Object entity1 = //
Object entity2 = //
serviceWrapper.save( entity1, entity2 );
// now use the repository to find the entity revision
}
Upvotes: 0