I am in reference to Spring Roo In Action (book from Manning). Somewhere in the book it says "Roo marks the test class as #Transactional so that the unit tests automatically roll back any change.
Here is the illustrating method:
#Test
#Transactional
public void addAndFetchCourseViaRepo() {
Course c = new Course();
c.setCourseType(CourseTypeEnum.CONTINUING_EDUCATION);
c.setName("Stand-up Comedy");
c.setDescription(
"You'll laugh, you'll cry, it will become a part of you.");
c.setMaxiumumCapacity(10);
c.persist();
c.flush();
c.clear();
Assert.assertNotNull(c.getId());
Course c2 = Course.findCourse(c.getId());
Assert.assertNotNull(c2);
Assert.assertEquals(c.getName(), c2.getName());
Assert.assertEquals(c2.getDescription(), c.getDescription());
Assert.assertEquals(
c.getMaxiumumCapacity(), c2.getMaxiumumCapacity());
Assert.assertEquals(c.getCourseType(), c2.getCourseType());
}
However, I don't understand why changes in this method would be automatically rolled back if no RuntimeException occurs...
Quote from documentation:
By default, the framework will create and roll back a transaction for each test. You simply write code that can assume the existence of a transaction. [...] In addition, if test methods delete the contents of selected tables while running within a transaction, the transaction will roll back by default, and the database will return to its state prior to execution of the test. Transactional support is provided to your test class via a PlatformTransactionManager bean defined in the test's application context.
So, in other words, SpringJUnit4ClassRunner who runs your tests always do transaction rollback after test execution.
I'm trying to find a method that allows me to do a rollback when one of the elements of a list fails for a reason within the business rules established (ie: when throw my customize exception)
Example, (the idea is not recording anything if one element in list fails)
public class ControlSaveElement {
public void saveRecords(List<MyRecord> listRecords) {
Boolean status = true;
foreach(MyRecord element: listRecords) {
// Here is business rules
if(element.getStatus() == false) {
// something
status = false;
}
element.persist();
}
if(status == false) {
// I need to do roll back from all elements persisted before
}
}
...
}
Any idea? I'm working with Roo 1.2.2..
Related
The Repeatable Read isolation level only sees data committed before the transaction began.
So, I think it is only meaningful at Method doing repeat select.
But, I see a below code.
#Transactional(isolation = Isolation.REPEATABLE_READ)
#Override
public void buyItem(Credit playerCredit, Long haveToPayCredit) {
if(haveToPayCredit > playerCredit.getFreeCredit()) {
Long remainHaveToPay = haveToPayCredit - playerCredit.getFreeCredit();
playerCredit.updateCredit(playerCredit.getPaidCredit() - remainHaveToPay, 0L);
}
else {
playerCredit.updateCredit(playerCredit.getPaidCredit(), playerCredit.getFreeCredit() - haveToPayCredit);
}
}
This method do only update. So, It look so meaningless to add Transactional annotation.
The REPEATABLE READ isolation level at update method is meaningful?
I'm writing a unit test for a Flutter method that calls an async method and then returns, leaving the async to complete as and when. My test fails "after it had already completed".
Here's my test:
test('mark as viewed', () {
final a = Asset();
expect(a.viewed, false);
a.markAsViewed();
expect(a.viewed, true);
});
and here's the method it's testing:
void markAsViewed() {
viewed = true;
Repository.get().saveToStorage();
}
The saveToStorage() method is an async that I just leave to execute in the background.
How do I make this work? The test failure tells me Make sure to use [expectAsync] or the [completes] matcher when testing async code. but I can't see how to do that. Can anyone explain or else point me to the right documentation please? I can't find anything about how to handle these asyncs when it's not a Future that's being returned, but just being left to complete separately.
To be clear - this unit test isn't about testing whether it's saved to storage, just a basic test on setting viewed to be true.
Edited
The error is as follows:
package:flutter/src/services/platform_channel.dart 319:7 MethodChannel.invokeMethod
===== asynchronous gap ===========================
dart:async _asyncErrorWrapperHelper
package:exec_pointers/asset_details.dart Repository.saveToStorage
package:exec_pointers/asset_details.dart 64:22 Asset.markAsViewed
test/asset_details_test.dart 57:9 main.<fn>.<fn>
This test failed after it had already completed. Make sure to use [expectAsync]
or the [completes] matcher when testing async code.
This code is tightly coupled to implementation concerns that make testing it in isolation difficult.
It should be refactored to follow a more SOLID design with explicit dependencies that can be replaced when testing in isolation (unit testing)
For example
class Asset {
Asset({Repository repository}) {
this.repository = repository;
}
final Repository repository;
bool viewed;
void markAsViewed() {
viewed = true;
repository.saveToStorage();
}
//...
}
That way when testing a mock/stub of the dependency can be used to avoid any unwanted behavior.
// Create a Mock Repository using the Mock class provided by the Mockito package.
// Create new instances of this class in each test.
class MockRepository extends Mock implements Repository {}
main() {
test('mark as viewed', () {
final repo = MockRepository();
// Use Mockito to do nothing when it calls the repository
when(repo.saveToStorage())
.thenAnswer((_) async => { });
final subject = Asset(repo);
expect(subject.viewed, false);
subject.markAsViewed();
expect(subject.viewed, true);
//
verify(repo.saveToStorage());
});
}
The test should now be able to be exercised without unexpected behavior from the dependency.
Reference An introduction to unit testing
Reference Mock dependencies using Mockito
Reference mockito 4.1.1
I have an event handler class as follows:
#Component
#RepositoryEventHandler(DonationOffer.class)
public class DonationOfferEventHandler {
#Autowired
DonationOfferNotificationService notificationService;
#HandleAfterSave
public void handleAfterSave(DonationOffer donationOffer){
Integer donationOfferId = donationOffer.getDonationOfferId();
System.out.println("updating donation offer");
switch (donationOffer.getOfferStatus()){
case Accepted:
notificationService.generateAcceptedNotification(donationOfferId);
break;
case Rejected:
notificationService.generateRejectedNotification(donationOfferId);
break;
case Cancelled:
notificationService.generateCancelledNotification(donationOfferId);
break;
}
}
}
And spring repo as follows:
#Transactional
public interface DonationOfferRepo extends PagingAndSortingRepository<DonationOffer,Integer>{
#Modifying
#Query(
"update DonationOffer d " +
"set offerStatus = :offerStatus " +
"where d.donationOfferId = :donationOfferId"
)
#RestResource(path="updateStatus")
int updateStatus(
#Param("donationOfferId") Integer donationOfferId,
#Param("offerStatus") OfferStatus offerStatus
);
}
Event handler is called in case of PUT request by the problem is that #HandleAfterSave method is not being called when I call updateStatus() i.e.,
a get request to /api/donationOffers/search/updateStatus?donationOfferId=45&offerStatus=Cancelled
Can any one help if I am missing something or is it the default behavior that #HandleAfterSave would be called only with PUT request ?
UPDATE:
I have created a method to test event handler with repo save method as follows. Event handler is not called in this case also.
#GetMapping(value="/savetest")
#Transactional
public ResponseEntity saveTesT(){
DonationOffer donationOffer = donationOfferRepo.findOne(1);
donationOffer.setOfferedBottles(donationOffer.getOfferedBottles()+1);
donationOfferRepo.save(donationOffer);
return new ResponseEntity(new StringWrapper("OK"), HttpStatus.OK);
}
In order to trigger event from HandleBeforeSave you need to execute PUT request. If you are doing POST request than the correct event handler would be HandleBeforeCreate.
UPDATE:i would guess that the listener method does not get invoked for the same reason as to why Queries do not invoke JPA prePersist, postPersist, preUpdate ... and so on events. Queries have different execution route as to EntityManager.persis , update. Entity manager works on the foundation of identity , therefore it can guarantee that an entity has actually been updates. Same can not be told when you use a Query. Normally entity manager would execute one query to fetch the entity than it will be analysed for changes and then UPDATE will happen, or will not happen in case of no changes. There is no mechanism to tell if update has really happened when query is executed.
According to the documentation #Modifying annotation is not guaranteeing that modification will actually happen. What it is guaranteeing is that the session will be cleared after method invokation. The reasoning behind this is again that the queries have different execution route than the normal persist, merge methods and you can not guarantee that the session will keep integrity. Therefore #Modifying will guarantee it has been cleared.
I have the following method in an #Service class which has #Transactional defined:
#Override
public Result add(#NonNull final UserSaveRequest request) {
final Result<Email> emailResult = Email.create(request.getEmail());
final Result<UserFirstName> userFirstNameResult = UserFirstName.create(request.getFirstName());
final Result<UserLastName> userLastNameResult = UserLastName.create(request.getLastName());
final Result combinedResult = Result.combine(emailResult, userFirstNameResult, userLastNameResult);
if (combinedResult.isFailure()) {
return Result.fail(combinedResult.getErrorMessage());
}
final Result<User> userResult = User.create(emailResult.getValue(), userFirstNameResult.getValue(), userLastNameResult.getValue());
if (userResult.isFailure()) {
return Result.fail(userResult.getErrorMessage());
}
this.userRepository.save(userResult.getValue());
return Result.ok();
}
Now as you can see I utilize a Result class which can contain a return value or an error message as I don't think using exceptions for flow control is very clean.
The problem I now have is; the complete method is bound in one transaction and if one database call should fail the whole transaction will be rolled back. In my model however, after the this.userRepository.save(userResult.getValue()); call, if something would happen that would force me to return a failed result, I can't undo that save(userResult.getVlaue()); call seeing as I don't use exceptions for flow control.
Is this a problem that has an elegant solution, or is this a place where I need to make a trade-off between using exceptions as flow control and having to mentally keep track of the ordering of my statements in these kind of situations?
Yes, you can trigger rollback manually. Try this:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
More information: https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/data-access.html#transaction-declarative-rolling-back
I can't seem to find this on their site or here. I'm hoping the answer is YES. Specifically I want to write a Verifier to check for unique key violations (I know I will have to raise the isolation level to Serializable). This won't work unless the Verifier runs in the same transaction as the Save.
DevForce does not use the same transaction for validation and save processing. Within the context of an EntityServerSaveInterceptor, authorization and validation are performed and then a TransactionScope is opened when doing the actual save. If you do a query within a verifier it will use a separate TransactionScope.
You can work around this behavior with a little extra work in your custom EntityServerSaveInterceptor. Override the ValidateSave function to bypass validation, then override the ExecuteSave method to open a TransactionScope and then do your validation logic before calling the base save logic. The TransactionScope opened by DF during the save will enlist in your TransactionScope. Something like this:
public class EntityServerSaveManager : EntityServerSaveInterceptor {
protected override bool ValidateSave() {
// will do validation later
return true;
}
protected override bool ExecuteSave() {
using (var ts = new TransactionScope(TransactionScopeOption.Required, this.SaveOptions.TransactionSettings.ToTransactionOptions())) {
// Do validation logic now
...
// Now do save
base.ExecuteSave();
ts.Complete();
}
}
}