When the JUnit Test bellow executes an INSERT or DELETE, the table gets locked and the service being tested cannot read the table. It's also not possible to read any row in the table outside Eclipse, from SSMS, for example.
One thing to know is, all test classes extend from a BaseTest class, from which I autowire the repositories, which are the same classes used in the service being tested. Not sure it is a problem.
class AlterarComiteTest extends BaseTest {
[...]
}
#ActiveProfiles("des")
#Slf4j
#SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class BaseTest {
[...]
#Autowired
protected ComiteRepository comiteRep;
[...]
}
This is how the controller method being tested looks like:
[...]
#PutMapping("/{sqComite}")
#Transactional(value = TxType.REQUIRES_NEW, rollbackOn = Throwable.class)
public ResponseEntity<ComiteDto> atualizarComite(HttpServletRequest request, #PathVariable Long sqComite, #RequestBody #Valid ComiteForm form) throws ApplicationException {
}
[...]
Try #1
Result: After cadastrarComite(true), which executes an INSERT on the same entity of the controller, the table gets locked. When the service method tries to SELECT the entity, it finds the lock and remain there, waiting.
Note: excluirComite(comiteCenario) DELETEs the entity.
#Test
#Transactional
void alterar_DadosInvalidos_SqPeriodicidadeReuniaoNaoExiste() {
[...]
Comite comiteCenario = cadastrarComite(true);
[...]
[calls the service method being tested]
[...]
excluirComite(comiteCenario);
}
Try #2
Result: Same as Try #1, but the lock starts in the #BeforeEach
class AlterarComiteTest extends BaseTest {
private Comite comiteAtivo;
#BeforeEach
void setUpBeforeEach() {
[...]
if (comiteAtivo == null) {
comiteAtivo = cadastrarComite(true);
}
[...]
}
#Test
#Transactional
void alterar_DadosInvalidos_SqPeriodicidadeReuniaoNaoExiste() {
[...]
Comite comiteCenario = comiteAtivo;
[...]
[calls the service method being tested]
[...]
excluirComite(comiteCenario);
}
}
Try #3
Result: If all tests in the test class are executed, then they all execute successfully, but the #AfterAll fails, since it (for some reason) does not have any transaction related to it. It causes created entities to not be DELETED, dirtying the database.
Caused by: javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call
BUT, if only any single #Test from the test class is executed, then it works all the way (maybe the test transaction is used in the #AfterAll, don't know why).
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AlterarComiteTest extends BaseTest {
private Comite comiteAtivo;
#BeforeAll
void setUpBeforeClass() {
comiteAtivo = cadastrarComite(true);
}
#AfterAll
void tearDownAfterClass() {
excluirComite(comiteAtivo);
}
#Test
#Transactional
void alterar_DadosInvalidos_SqPeriodicidadeReuniaoNaoExiste() {
[...]
Comite comiteCenario = comiteAtivo;
[...]
[calls the service method being tested]
}
}
Try #4
Result: Removed #Transactional from all test methods. All tests got the same error.
Caused by: javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call
#BeforeEach
void setUpBeforeEach() {
comiteAtivo = cadastrarComite(true);
comiteInativo = cadastrarComite(false);
}
#AfterEach
void tearDownAfterEach() {
excluirComite(comiteAtivo);
excluirComite(comiteInativo);
}
#Test
void alterar_DadosInvalidos_SqPeriodicidadeReuniaoNaoExiste() {
[...]
Comite comiteCenario = comiteAtivo;
[...]
[calls the service method being tested]
}
Last note: I tried inserting #Transactional on cadastrarComite and excluirComite as well. Currently they do not have it. It did not made any difference.
Related
Background Info
I have a thread. It's a dedicated thread to continuously take out a task from a queue and write to a persistent repository. So it's code is like this.
public class Processor extends Thread {
//Context saves reference to the task queue and the write backEnd service
public GeneralProcessor(Context context){initProcessor( context, Type.GENERAL);}
public void run() {
...
synchronized (Processor.class) {
Thread curThread=currentThread();
Context context=getContext();
ConcurrentLinkedQueue<Task> taskQue =context.getTasks();
if (taskQue.size() > 0) {
Task t = taskQue.poll();
Transaction ts = new Transaction();
//all works to copy Task's member values to Transaction, and add other values to this transaction
//...
context.getService().save(ts);//this is the line I want to monitor. This is also related to issue in some way.
}
}
}
}
The Issue
But there is an issue when I wrote a unit test for this class. My original unit test is this.
#ExtendWith(MockitoExtension.class)
public class GeneralProcessorTest {
#InjectMocks
private GeneralProcessor generalProcessor;
#Mock
private Context context;
#Spy
private ConcurrentLinkedQueue<Task> tasks;
#Mock
private TransactionRepository transactionRepository;
#Captor
private ArgumentCaptor<Transaction> transactionArgumentCaptor;
#Mock
private TransactionService transactionService;
#BeforeEach
void setup() {
//transactionService=new TransactionServiceImpl(transactionRepository);
}
#Test
#SneakyThrows
void should_match_save_times_single_thread() {
//given
CountDownLatch latch=new CountDownLatch(0);
this.tasks.add(new Task(10));
//stub code
when(context.getTasks()).thenReturn(tasks);
when(context.getService()).thenReturn(transactionService);
//when
generalProcessor.start();
latch.await(1, TimeUnit.SECONDS);
//then
//the issue happened here!
verify(transactionService).save(transactionArgumentCaptor.capture());
List<Transaction> capturedArguments = transactionArgumentCaptor.getAllValues();
assertEquals(capturedArguments.size(),1);
}
But I got:
Wanted but not invoked:
transactionService.save(
<Capturing argument>
);
at com.example.demo.GeneralProcessorTest.should_match_save_times_single_thread(GeneralProcessorTest.java:65)
Actually, there were zero interactions with this mock.
In fact, I tried to init transactionService with new. But Mockito told me that in verify I can only use Mock object.
So I am confused. Is there any way to let me use verify while at the same time keep transactionService working as a normal object? Any info is appreciated.
We are using Project Reactor to run a particular operation asynchronously as per the code in ServiceTest below. To test this code, as per ServiceTest below, when setting up the Mono for the async operation we make the Mono pass it's result to a DirectProcessor with doOnNext that the test has access to, and then carry out our test call and assertions with StepVerifier.
The JavaDoc of StepVerifier#assertNext reads
Any AssertionErrors thrown by the consumer will be rethrown during verification.
We have found that is true only when the immediate scheduler (Schedulers.immediate()) is used and is not true when the single scheduler (Schedulers.single()) is used. When the single scheduler is used, AssertionErrors are not re-thrown, i.e. the test always passes.
Is it possible, and if so, how, to use the single scheduler and have AssertionErrors rethrown during verification as per the JavaDoc?
#Service
#RequiredArgsConstructor
public class Service implements WithReactive, WithTestProcessor<Response> {
#Getter
#Setter
private DirectProcessor<Response> processor = DirectProcessor.create();
#Setter
private Scheduler scheduler = Schedulers.single();
public void doAction() {
Mono.fromSupplier(this::doActionAsync)
.doOnNext(processor::onNext)
.subscribeOn(scheduler)
.subscribe();
}
private Response doActionAsync() {
...
}
...
}
public interface WithReactive {
void setScheduler(Scheduler scheduler);
}
public interface WithTestProcessor<T> {
void setProcessor(DirectProcessor<T> processor);
DirectProcessor<T> getProcessor();
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class ServiceTest {
#Inject
private Collection<WithTestProcessor> withTestProcessors;
#Before
public void setTestProcessors() {
withTestProcessors.forEach(withTestProcessor -> withTestProcessor.setProcessor(DirectProcessor.create()));
}
#Inject
private Collection<WithReactive> withReactives;
#Before
public void makeReactiveSynchronous() {
withReactives.forEach(withReactive -> withReactive.setScheduler(Schedulers.immediate()));
}
#Test
private void test() {
StepVerifier.create(service.getProcessor())
.then(service::doAction)
.assertNext(response -> assertThat(logExtractor.getInsertsByTable("assets")).hasSize(1))
.thenCancel()
.verify();
}
}
This is a combination of three factors: the initial then, the fact that subscription happens in parallel of the verification due to subscribeOn and the thenCancel.
One workaround is to give enough time to the onNext to happen before the StepVerifier executes thenCancel, by putting a thenAwait(Duration.ofMillis(10)) before the thenCancel.
We use Spring's #DirtiesContext annotation quite liberally throughout our test code to guarantee no side effects between test classes. I recently learned about TestNG's #DataProvider and #Factory and some test in my code base would really benefit from this approach.
However, I found that #DirtiesContext causes the Spring beans to be shutdown too early, namely after the last test method of the first test instance the factory created.
A broken down version of my current setup looks like this
#ContextConfiguration(classes = { SomeSpringContex.class })
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
class TestClass {
#Autowired
Database database;
private final String configParameter;
#Factory(dataProvider = "testSetup")
public TestClass(String configParameter) {
this.configParameter = configParameter;
}
#DataProvider(name="testSetup")
public static Object[][] createParameters() {
return new Object[][] {
{ "param1" },
{ "param2" }
};
}
#BeforeMethod
public void useConfigParameter() {
// use configParameter to set up test
}
#Test
public void firstTest() {
database.query("SELECT * FROM foo");
}
#Test
public void secondTest() {
database.query("SELECT * FROM bar");
}
}
This essentially creates two instances of TestClass, one set up with "param1" and one set up with "param2". Thus every test is run twice, as intended, and the output (in eclipse) looks something like this
firstTest (with "param1") => SUCCESS
firstTest (with "param2") => SUCCESS
secondTest (with "param1") => SUCCESS
secondTest (with "param2") => FAIL, because the Database bean, along with it's database connection, was shutdown already
When I remove the #DirtiesContext annotation the test methods all work fine, but in my actual tests I run into inter test problems. Is there a way to make #DirtiesContext and #Factory play nicely together?
I am new to JUnit and trying to test a spring web service which uses JPA DAOs. I need to test a service method similar to below.
Service method is annotated with #Transactional(propagation=Propagation.REQUIRED) and ServiceObjectRepository.update() method calls a native sql query to update the db.
#Transactional(propagation=Propagation.REQUIRED)
public void serviceMethod(){
//Read DB for ServiceObject to update
//Call ServiceObjectRepository.update() method to update DB
}
ServiceObjectRepository
public interface ServiceObjectRepository extends JpaRepository<ServiceObject, Integer> {
#Query(value ="UPDATE serviceobjcet AS c SET c.objectstatus= :os WHERE c.objid = :oi", nativeQuery = true)
public Integer update(#Param("os")short objStatus,#Param("oi")int objId);
}
TestClass
#TransactionConfiguration(defaultRollback=true)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
locations = "classpath:service-test.xml")
#Transactional
public class ServiceHandlerTest {
#Test
public void testServiceMethod() {
//Create ServiceObject and save to DB
//Call serviceMethod()
//Read DB for updatedServiceObject
assertEquals("Test: Object should be in updated state", new Short(3), updatedServiceObject.getObjectstatus(), 0);
}
}
My test runs and rollback the db transactions. But the problem is when I read the updatedServiceObject after calling the serviceMethod it does not return the updated object. So my test fails with a NullPointerException at the assertEquals. Any idea to overcome this issue?
Finally I came up with a solution, Rather than creating ServiceObject and saving to DB in the test method, I have done it before the test method and deletion of the created objects done after the transaction. So that my test class looks like,
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:service-test.xml")
public class ServiceHandlerTest {
#Before
public void setup() {
//Create ServiceObject and save to DB
}
#Test
public void testServiceMethod() {
//Call serviceMethod()
//Read DB for updatedServiceObject
assertEquals("Test: Object should be in updated state", new Short(3), updatedServiceObject.getObjectstatus(), 0);
}
#After
public void teardown() {
//Delete created ServiceObject from DB
}
}
And I found that test method or test class does not necessary to be transactional in such a case.
I am making Test of My classes so I am inserting so many data for to test my code.
So I am thinking to make some mechanism of savepoint and rollback in DB.
I am using postgresql as DB sever.
Following is my code for test :
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("file:src/main/webapp/WEB-INF/ls-dispatcher-servlet.xml")
public class AddBindingProcessorTest extends IntegrationTestBase {
#Autowired
private AddBindingProcessor processor;
public AddBindingProcessorTest(){
}
#Before
public void setUp() throws Exception {
}
#After
public void tearDown() throws Exception {
}
#Test
public void add() throws Exception {
AddBinding command;
command = new AddBinding();
command.setId(50l);
command.setBindingName("bindingtest1");
command.setBindingPrice((double)253);
BindingTypeResponse response = (BindingTypeResponse)processRequest(command);
System.out.println("from addbindingprocessor test "+response.getBindingName());
}
}
Here I am setting value through command object and passing to ProcessRequest() Method that will store data inside DB using hibernate.
Still I have to write assert in my testProcess() method that will check data is correct or not ?
So my question is that I when this transaction starts in setUp() method one savepoint should be created and then testProcess() method will be executed and assert check for the data that they are correct or not and then in tearDown() method I want to rollback to savepoint that is set in setUp() method.
So how to do so ? If Anyone can just guide me that what I ll have use and how to move forward then I ll learn that thing and go by myself.
I just want guidance about it that what I ll have to use and where ?
Thank You All.
If I get you right, you can just use the
#TransactionConfiguration(defaultRollback = true)
annotation below your #ContextConfiguration annotation.
This will rollback the changes in your tests after every run.
user3145373 pointed out, that the attribute transactionManager="context bean transaction manager" in #TransactionConfiguration needed to be set also.
It is part of the spring-test lib.