I'm working on a unit test which commits information through a DAO to a Oracle database, and then retrieves it and checks everything is unchanged.
There are other unit tests in the test class, and at the top of the class I have:
#TransactionConfiguration (defaultRollback = true)
I'd like to know how I can remove the #NotTransactional. I'm not specifying Transactional on the class, so no tests should be like that by default. Since this is it's own test, I don't know if the #BeforeTransaction (or After) annotations would be correct.
The biggest problem is that without the #NotTransactional, it appears as though the unsubscribe() function isn't ran. (the trash flag is unchanged.)
Running the test again, with #rollback=false and #NonTransactional present, I see the trash flag correctly set to true, in the database after the test is finished.
#RunWith (SpringJUnit4ClassRunner.class)
#TransactionConfiguration (defaultRollback = true)
public class DatabaseTest {
#Autowired (required = true)
private FooDao<Foo> fooDao;
#Autowired (required = true)
private FooService fooService;
// other tests here that are marked #Transactional.
#Test
#NotTransactional
public void testDelete() {
Foo foo = new foo();
foo.setTrashFlag(false);
foo.setId(123);
fooDao.create(foo);
Foo fooFromDb = fooService.getFromDbById(123);
assertNotNull(fooFromDb);
fooService.unsubscribe(123); // among other things,
// **sets trash flag to true.**
// sqlSession.flushStatements(); // doesn't seem to commit the unsubscribe action
// getFromDbById() returns ONLY Foos with trash set to false, so we expect
// nothing returned here as we've just set trash to true, using unsubscribe().
Foo trashedFoo = fooService.getFromDbById(123);
assertNull(trashedFoo);
Thanks!
I don't know if this is really on-topic, but I used #NotTransactional for methods that had a transactional base class. Spring says to split the tests in transactional and non-transactional, but I found out that in my case simply adding the following to the method just disabled transactions and let me delete the deprecated annotation:
#Transactional(propagation = Propagation.NEVER)
I thought I just put it out here for people who run in the same thing I did.
#Rollback(false) isn't always sufficient, it only means the transaction won't be rolled back at the end of test. But it still runs the test in a transaction, which sometimes lead to conflicts with other running transactions.
As Spring suggest, you have two options:
Split the test class into two, the transactional tests can be a in test class annotated with #Transactional, while the non-transactional test in a test class without transactional annotation.
Use method annotations instead of class annotation, annotate each transaction test with #Transactional, but remove it from the class scope
Quoting Spring documentation:
As of Spring 3.0, #NotTransactional is deprecated in favor of moving
the non-transactional test method to a separate (non-transactional)
test class or to a #BeforeTransaction or #AfterTransaction method. As
an alternative to annotating an entire class with #Transactional,
consider annotating individual methods with #Transactional; doing so
allows a mix of transactional and non-transactional methods in the
same test class without the need for using #NotTransactional.
Related
In the following test code snippet, values of number_of_days.last and number_of_months.plan are not getting fetched from the configuration file.Please check and see what could be the reason.When i remove #Value annotation from my service class ShiftPlanService.java and just initialize the values there with the required values,the test passes.
#ExtendWith(MockitoExtension.class)
#ContextConfiguration(classes=SpringbootMysqlExampleApplication.class)
#TestPropertySource(locations="src/main/resources/application.properties",properties= {"number_of_days.last= 7","number_of_months.plan= 2"})
class ShiftPlanServiceTest {
#Mock
ShiftPlanRepo mockedSpr;
#Mock(lenient = true)
ShiftDetailsRepo mockedSdr;
#Mock(lenient = true)
EmployeeDetailsRepo mockedEdr;
#Spy
ShiftPlanService sps;
#BeforeEach
public void setUp() {
when(mockedSdr.findShiftNameById(1)).thenReturn("Morning");
when(mockedSdr.findShiftNameById(2)).thenReturn("Afternoon");
when(mockedEdr.getNameById(0)).thenReturn("Amit");
when(mockedEdr.getNameById(1)).thenReturn("Anupam");
when(mockedEdr.getNameById(2)).thenReturn("Chirag");
when(mockedEdr.getNameById(3)).thenReturn("Rashmi");
when(mockedEdr.count()).thenReturn(4L);
}
#Test
public void testCreateShiftPlan() {
sps.createShiftPlan(4, 1, 2020);
verify(mockedSpr, times(36)).save(any(ShiftPlan.class));
verifyNoMoreInteractions(mockedSpr);
}
}
application.properties file is as follows-
server.port=8104
number_of_days.last= 7
number_of_months.plan= 2
spring.datasource.url=<<sensitive info>>
spring.datasource.username=<sensitive info>
spring.datasource.password=<sensitive info>
#Keep the connection alive while idle for a long time
spring.datasource.testWhileIdle= true
spring.datasource.validationQuery= SELECT 1
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
# stripped before adding them to the entity manager)
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
In the ShiftPlanService class, i have
#Value("${number_of_days.last}")
public int ndl;
#Value("${number_of_months.plan}")
public int nm;
I think you are intending to use a real instance of ShiftPlanService and have mocks injected. You need to have Spring autowire the ShiftPlanService into your test and tell it to inject the mocks like this:
#Autowired
#InjectMocks
ShiftPlanService sps;
Though you may consider just instantiating ShiftPlanService yourself in your setup method and just pass your mocks and set the other properties on the ShiftPlanService instead.
You are confusing Mockito injection with Spring injection. #Value is a Spring concept and will only be injected when Spring manages the bean, but the instance of ShiftPlanService you have in your test is injected by Mockito using #Spy (which as has been pointed out you don't really need).
My recommendation would be to decide what you want - a unit test with mocks, or a full-blown Spring test with the application context running. It seems to me that your intent is to write a unit test with everything mocked out, in which case:
remove #ContextConfiguration and #TestPropertySource (you don't need those for unit tests)
use #InjectMocks instead of #Spy on ShiftPlanService sps - it will most likely do what you want, depending on how ShiftPlanService is implemented
manually set the config values you need in sps; you can add setters for those for the test to use; if the unit test is in the same package as the class - which is a good practice - they can be package-private too - because in production Spring will autowire them for you, so you only need them for the test
oh, and keep the #Value annotation in your ShiftPlanService - it's needed for production - as explained above
We invented a Mockito extension that allows for easy injection of String/Integer/Boolean properties. Checkout the readme, it's super easy to use and works along with #InjectMocks
https://github.com/exabrial/mockito-object-injection
REQUIRES_NEW is rolling back all transactions:
I have a method marked #Transactional(propagation = REQUIRES_NEW)
within a bean that also has methods marked #Transactional(propagation = REQUIRED). These methods never call each other. They are called from another bean individually. However when my method that has been marked with REQUIRES_NEW fails, it rolls back the whole transaction, including the methods that are marked REQUIRED (which is problematic).
My understanding is that if factored this way Spring AOP will have a chance to intercept the REQUIRES_NEW method and start a new logical transaction.
The general idea looks like this:
#Transactional
class TransactionalBean{
#Transactional(propagation = REQUIRED)
public void methodA(){ //do transactional work }
#Transactional(propagation = REQUIRES_NEW)
public void methodB(){ //do transactional work }
}
And then the calling bean looks like so:
#Transactional
class CallingBean{
#Autowired
TransactionalBean
public void doWork(){
TransactionalBean.methodA();
TransactionalBean.methodB();
}
}
So if method A and B succeed, everything is fine. However when method B fails work done in method A gets rolled back. My understanding is that when methodB() is invoked that it 'should' get intercepted by AOP and start a new transaction and suspend the other transaction, but it's not working. How do I fix this? I'm using Grails 2.5, Hibernate, JPA
The problem is with the way the CallingBean was marked as #Transactional. What is happening is that the CallingBean is creating a transaction, let's say t1. The CallingBean is invoking the two transactional methods methodA and methodB. Since methodA requires a transaction, it will make use of the existing transaction t1. However, methodB will create a new transaction as the annotation requires a new one, let's say t2. The transactional boundary of methodA does not end when the control exits methodA, but is kept alive until methodB is completed since the transaction started at the calling bean. Now that the transaction t2 is failing in methodB, the exception would bubble up to the calling bean and the transaction t1 would fail.
In order to resolve this, you can do one of the following based on your needs.
Turn off the #Transactional annotation in the CallingBean class.
//#Transactional
class CallingBean{
...
}
Update the annotation to use noRollbackFor option
#Transactional(noRollbackFor={WhateverException.class})
class CallingBean{
...
}
I've been trying to test out #TransactionalEvents (a feature of Spring 4.2 https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2) with our existing Spring JUnit Tests (run via either #TransactionalTestExecutionListener or subclassing AbstractTransactionalUnit4SpringContextTests but, it seems like there's a forced choice -- either run the test without a #Rollback annotation, or the events don't fire. Has anyone come across a good way to test #TransactionalEvents while being able to #Rollback tests?
Stéphane Nicoll is correct: if the TransactionPhase for your #TransactionalEventListener is set to AFTER_COMMIT, then having a transactional test with automatic rollback semantics doesn't make any sense because the event will never get fired.
In other words, there is no way to have an event fired after a transaction is committed if that transaction is never committed.
So if you really want the event to be fired, you have to let the transaction be committed (e.g., by annotating your test method with #Commit). To clean up after the commit, you should be able to use #Sql in isolated mode to execute cleanup scripts after the transaction has committed. For example, something like the following (untested code) might work for you:
#Transactional
#Commit
#Sql(scripts = "/cleanup.sql", executionPhase = AFTER_TEST_METHOD,
config = #SqlConfig(transactionMode = TransactionMode.ISOLATED))
#Test
public void test() { /* ... */ }
Regards,
Sam (author of the Spring TestContext Framework)
Marco's solution works but adding REQUIRES_NEW propagation into business code is not always acceptable. This modifies business process behavior.
So we should assume that we can change only test part.
Solutin 1
#TestComponent // can be used with spring boot
public class TestApplicationService {
#Autowired
public MyApplicationService service;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething() {
service.doSomething();
}
}
This wraps the real service into test component that can be decorated with REQUIRES_NEW propagation. This solution doesn't modify other logic than testing.
Solution 2
#Transactional
#Sql(scripts = "/cleanup.sql", executionPhase = AFTER_TEST_METHOD,
config = #SqlConfig(transactionMode = TransactionMode.ISOLATED))
#Test
public void test() {
MyApplicationService target = // ...
target.doSomething();
TestTransaction.flagForCommit(); //Spring-test since 4.1 - thx for Sam Brannen
TestTransaction.end();
// the event is now received by MyListener
// assertions on the side effects of MyListener
// ...
}
This is simplest solution. We can end test transaction and mark it for commit. This forces transactional events to be handled. If test changes data, cleanup sql script must be specified, otherwise we introduce side effects with committed modified data.
Sam Brannen's solution almost works with regard to adam's comment.
Actually the methods annotated with #TransactionalEventListener are called after the test method transaction is committed. This is so because the calling method, which raises the event, is executing within a logical transaction, not a physical one.
Instead, when the calling method is executed within a new physical transaction, then the methods annotated with #TransactionalEventListener are invoked at the right time, i.e., before the test method transaction is committed.
Also, we don't need #Commit on the test methods, since we actually don't care about these transactions. However, we do need the #Sql(...) statement as explained by Sam Brannen to undo the committed changes of the calling method.
See the small example below.
First the listener that is called when the transaction is committed (default behavior of #TransactionalEventListener):
#Component
public class MyListener {
#TransactionalEventListener
public void when(MyEvent event) {
...
}
}
Then the application service that publishes the event listened to by the above class. Notice that the transactions are configured to be new physical ones each time a method is invoked (see the Spring Framework doc for more details):
#Service
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class MyApplicationService {
public void doSomething() {
// ...
// publishes an instance of MyEvent
// ...
}
}
Finally the test method as proposed by Sam Brannen but without the #Commitannotation which is not needed at this point:
#Transactional
#Sql(scripts = "/cleanup.sql", executionPhase = AFTER_TEST_METHOD,
config = #SqlConfig(transactionMode = TransactionMode.ISOLATED))
#Test
public void test() {
MyApplicationService target = // ...
target.doSomething();
// the event is now received by MyListener
// assertions on the side effects of MyListener
// ...
}
This way it works like a charm :-)
I am new with spring/grails transaction and i am still not clear after reading so i am posting it in here,
I have a service class which is annotated as #Transactional
and i have methods some of them are annotated as
#Transactional(propagation = Propagation.REQUIRES_NEW)
and some of them are not.
#Transactional
class SomeService {
def findJob() {
MyInstance myInstance = getMeAJob();
if (myInstance) {
doSomeThing(myInstance)
doTask()
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception)
private doSomeThing(MyInstance myInst) {
myObj = MyInstance.lock(myInst.id)
try {
differentObj = doTask(myObj)
myObj.save()
doAnotherThing()
}
} catch (Exception e) {
log("Error in doAnotherTask")
}
}
private doAnotherThing(MyInstace myInst) {
perform some update on myInst
myInstant.save(flush: true)
}
private doTask() {
}
Suppose , I have transaction t1 from the class level and t2
transaction from doSomething()- REQUIRES_NEW.
Method that will execute in t1 - findJob() and doTask()
Method that will execute in t2 - doSomeThing() - doesn't affect the
"t1" in case of exception (Rollback)
Which one will be for doAnotherThing() method? Since I am calling it from doSomething()?
When you have a class-scope annotation, any method that's not annotated uses those settings, and annotated methods override the class-level settings with the settings from that annotation.
Note however that when using Spring's annotation support (with the #org.springframework.transaction.annotation.Transactional) annotation only public methods are supported. There won't be an error, but the annotations are silently ignored.
But even if you make doSomeThing public (either implicitly by removing the private keyword or explicitly by changing it to public) it won't do what you expect when you call doSomeThing from findJob. This is because Spring's annotation triggers the creation of a proxy of your class at runtime. The Spring bean that's registered for your service in that case is a proxy instance that has a "real" instance of your class as its delegate. All transactional public methods are intercepted by the proxy, which starts/joins/etc. a transaction and then calls your service method. Once you're "underneath" the proxy in the service instance all method calls are direct and any annotation settings have no effect. To get it to work you would need to call the method on the Spring bean and go through the proxy to start a new transaction.
To avoid this proxy issue, use the Grails #grails.transaction.Transactional annotation instead. It supports the same transaction features as the Spring annotation, but instead of creating a proxy, an AST transformation rewrites your methods to wrap them within a transaction. This makes it possible to make direct method calls like you're doing and create a new transaction or run under other transaction semantics as defined by the annotation attributes.
I did a talk a while back on using transactions in Grails that might shed some light.
I have a test class that looks like
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:/test-context.xml"})
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class TestClass {
#Rule #Resource public JUnitRuleMockery jMockContext;
public void test1() {
//Expectations and test
}
public void test2() {
//Expectations and test
}
}
and in test-context.xml I define the JUnitRuleMockery plus several mock objects through a factory-method, like
<bean id="mockContextFactory" class="MockContextFactory" />
<bean id="jMockContext" factory-bean="mockContextFactory" factory-method="getContext" scope="prototype" />
<bean id="firstMock" factory-bean="mockContextFactory" factory-method="getFirstMock" />
<bean id="secondMock" factory-bean="mockContextFactory" factory-method="getSecondMock" />
MockContextFactory is
public class MockContextFactory
{
private JUnitRuleMockery jUnitRuleMockery;
public MockContextFactory() {
jUnitRuleMockery = new JUnitRuleMockery();
jUnitRuleMockery.setThreadingPolicy(new Synchroniser());
}
public JUnitRuleMockery getContext() {
return jUnitRuleMockery;
}
public FirstMock getFirstMock() {
return jUnitRuleMockery.mock(FirstMock.class);
}
//others getter
}
In TestClass I have several test methods and, due to the annotations #DirtiesContext, I am expecting the Spring context to be reloaded after each test execution (since each test sets expectations on mock objects, I have to reload Spring context every time). See #DirtiesContext from here. However, it appears that the Spring context is not reloaded: in fact, entering in debug mode at the beginning of test2 (supposedly test1 has been executed earlier) I can see jMockContext still holding expectations, execution list and errors (if any) from test1.
So, to end up with few questions, does #DirtiesContext really cause Spring context to be reloaded (as I understood from Spring Docs) or did I misunderstand the annotation? In the first case, what am I doing wrong? In the latter case, how can I force Spring context to be reloaded for every test?
EDIT, to delimit the problem: I had a code like the sample above running from few days, then today I created a new test in which I added an expectation which failed. Then, I saw all the other tests in the class failing for the same reason (while they where green until today). Debugging, I found out that jMockContext was never cleared, which means all tests were adding expectations to the same pool: of course, as long as no expectation failed, that was transparent and I did not notice it (test1 adds and passes few expectations, test2 takes a not-empty pool of expectations ALREADY passed and adds its own so there was no problem), but I do not like the previous situation and I would like to start each test with no previous set expectations at all.
Here is my solution. I do not know if this is limited to Spring 3.2 or if it is a general misunderstanding, but simply using #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) is not enough to cause the entire Spring context to be reloaded for each test. I had to add also the DirtiesContextTestExecutionListener in #TestExecutionListeners.
So, in the end, what worked for me was just to change the annotation of my TestClass to
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:/test-context.xml"})
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
#TestExecutionListeners({DirtiesContextTestExecutionListener.class})
public abstract class TestClass
Nothing else has to change.