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.
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.
I'm new to Spring and trying to implement Spring Retry with a simple test.
however i can't make it work, hope someone could show me where i've done wrong.
Also I'm wondering, is it possible to write unit test to verify that Spring Retry has tried the requested maximum number of retries? because so far from google search, it seems it can only work in integration test because it needs Spring to set up the context first.
here is my main class:
#SpringBootApplication
public class SpringtestApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(SpringtestApplication.class).run(args);
}
}
the configuration class
#Configuration
#EnableRetry
public class FakeConfiguration implements ApplicationRunner {
private final FakeParser fakeParser;
public FakeConfiguration(FakeParser fakeParser) {
this.fakeParser = fakeParser;
}
#Override
public void run(ApplicationArguments args) {
this.runParser();
}
#Retryable(maxAttempts = 5, value = RuntimeException.class)
public void runParser() {
fakeParser.add();
}
}
the component/service class:
#Component
public class FakeParser {
public int add(){
int result = 113;
return result;
}
}
the retry test for it:
#RunWith(SpringRunner.class)
#SpringBootTest
class SpringtestApplicationTests {
#Autowired
private FakeConfiguration fakeConfiguration;
#MockBean
private FakeParser fakeParser;
#Test
public void retry5times(){
when(fakeParser.add()).thenThrow(RuntimeException.class);
try {
fakeConfiguration.runParser();
} catch (RuntimeException e){
}
verify(fakeParser, times(5)).add();
}
}
however, the test didn't pass:
org.mockito.exceptions.verification.TooManyActualInvocations:
fakeParser bean.add();
Wanted 5 times:
-> at com.example.springtest.SpringtestApplicationTests.retry5times(SpringtestApplicationTests.java:43)
But was 6 times:
-> at com.example.springtest.FakeConfiguration.runParser(FakeConfiguration.java:26)
-> at com.example.springtest.FakeConfiguration.runParser(FakeConfiguration.java:26)
-> at com.example.springtest.FakeConfiguration.runParser(FakeConfiguration.java:26)
-> at com.example.springtest.FakeConfiguration.runParser(FakeConfiguration.java:26)
-> at com.example.springtest.FakeConfiguration.runParser(FakeConfiguration.java:26)
-> at com.example.springtest.FakeConfiguration.runParser(FakeConfiguration.java:26)
when(someObject.someMethod()) evaluates the method and makes a real call to it. That's why you're always getting one more invocation than wanted.
If you need to count the actual invocations you could either add 1 to your verify, but that is an ugly workaround that is not recommended (and also not needed). Or you can use the Mockito.doXXX methods that don't have that problem.
In your case you could try
doThrow(new RuntimeException()).when(fakeParser).add();
This should give you the correct amount of invocations in the end. Notice the difference in the usages of when here: when(fakeParser).add() (two methods chained together) vs when(fakeParser.add()) (only one method)
Most probably you try with
Mockito.verify(fakeParser,times(5)).add(Mockito.any());
You should think about first run because when you retry 5 times and run once again. It should be 6 run even if you retry 5 times.
You are forgetting first exceptional case which is normal run behaviour
Is this the correct way to use #Async in Spring Boot?
#Service
class someServiceImpl {
...
public someResponseDTO getUsers(int userId) {
// Do some logic
...
// Call external API with another service method from another service impl
anotherService.emailUserInTheBackground(userId);
return someResponseDTO;
}
...
}
#Service
public class AnotherService {
#Async
public void emailUserInTheBackground(int userId) {
// This might take a while...
...
}
}
Since emailUserInTheBackground() has #Async annotation and void return type, does it block the line return someResponseDTO at all?
All I wanted is to return the response to the caller without waiting because emailUserInTheBackground() takes too long to complete and isn't directly tied to the response object.
Yes that is the correct way to run a task in the background, you can mimick the thread blocking behavior by introducing a delay.
#SpringBootApplication
#EnableAsync
public class MyApplication {
public static void main(String[] arg) {
SpringApplication.run(MyApplication.class);
}
}
then you need to mark the emailUserInTheBackground method with #Async annotation.
#Service
class AnotherService {
#Async
public void emailUserInTheBackground(int userId) {
try {
TimeUnit.SECONDS.sleep(10);
System.out.println("Print from async: "+ Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Now add one more logger after a method call, you'll see getUsers(...) call completing first in a different thread even though the emailService thread is blocked for 10 seconds.
anotherService.emailUserInTheBackground(userId);
System.out.println("Print from service: "+ Thread.currentThread().getName());
you can also use CompletableFuture to run a task in the background.
public someResponseDTO getUsers(int userId) {
// some other task
...
// Call external API with another service method from another service impl
CompletableFuture.runAsync(() -> anotherService.emailUserInTheBackground(userId))
return someResponseDTO;
}
The relevant behavior of #Async is documented in the Spring documentation:
You can provide the #Async annotation on a method so that invocation of that method occurs asynchronously. In other words, the caller returns immediately upon invocation, while the actual execution of the method occurs in a task that has been submitted to a Spring TaskExecutor.
In the case you're describing, since the emailUserInTheBackground method is annotated with #Async and Spring's asynchronous method execution capability is enabled, the emailUserInTheBackground method will return immediately, and the call will be processed in a separate thread. The someResponseDTO value will be be returned from the getUsers method while the emailUserInTheBackground method continues to be processed in the background.
I'm using mockito with Spring Boot. I found the stubs not work when using the mocked object in a Runnable.
Here is the code sample:
#Component
public class TheClassIWantTest {
#Autowired
private ADependency aDependency;
#Autowired
private ThreadPoolTaskExecutor executor;
public void theMethodIWantTest {
executor.execute(new Runnable() {
#Override
public void run() {
Integer result = aDependency.doSomething(); // result should be 111, but it's null
}
})
}
}
The unit test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class)
#FixMethodOrder(value = MethodSorters.NAME_ASCENDING)
public class TheTest {
#Autowired
private TheClassIWantTest theClassIWantTest;
#MockBean
private ADependency aDependency;
#Test
public void testTheMethod() {
Ingeter testResult = 1;
when(aDependency.doSomething()).thenReturn(111); // this stub doesn't work...
theClassIWantTest.theMethodIWantTest();
}
}
I was expecting aDependency.doSomething() would return 111, because I defined the stub in the test case, but the result is null.
And if I remove the lines of
executor.execute(new Runnable() {
#Override
public void run() {
to turn the async runnable method to a normal sync method, the stub works good,e.g result == 111.
This makes me think the stubs don't work anymore in a runnable.
How can I fix this? Or is there a workaround?
Thanks!
Have you considered that your test may actually be running faster than your Runnable? The executor service is asked to run the Runnable code and will do that in a separate thread (taken from a ThreadPool). Meanwhile, your test class continues on the main thread. You may want to look at https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html that will allow you pause your test code until your business code has counted down on your latch. Note that you are modifying your business code to accommodate for testing. Unit testing multi-threaded code is never easy...
Below is a simplified setup of my application. It has a class Foobar which calls on a facade method for fetching data. The facade then calls on a web service to actually get the data and then manipulates the data a bit and then returns it to Foobar.
Now because the web service might take a good while to run, the method call to the facade needs to be asynchronous. Hence the facade's method doesn't have a return value, but instead, the method uses a callback object. Look at the example and continue reading below.
public class Foobar {
private List<DTO> dtos;
#Autowired
private Facade facade;
public void refresh() {
facade.refreshFoobar(new CallBack() {
public void dataFetched(List<DTO> dtos) {
setDtos(dtos);
}
});
}
public void setDtos(List<DTO> dtos) {
this.dtos = dtos;
}
}
public class Facade {
...
public void refreshFoorbar(CallBack cb) {
// Fetch data from a web service
List<DTO> dtos = webService.getData();
// Manipulate DTOs
....
// call on the callback method
cb.dataFecthed(dtos);
}
}
I have two ways of making the facade's method asynchronous, either by creating a thread manually or by using springs #Async annotation.
public class Facade {
public void refreshFoorbar(CallBack cb) {
new Thread() {
#Override
public void run() {
....
}
}.start();
}
}
// ... OR ...
public class Facade {
#Async
public void refreshFoorbar(CallBack cb) {
....
}
}
My problem is that I now need to write an integration test for this chain of method calls. I think I need to force the async facade call to be synchronous when the integration test is ran, otherwise I won't know for sure when I can do the appropriate asserts. The only idea for making the method call synchronous is to use manually handled threads AND making the threading conditional (so, for testing purposes, I have an if clause which determines if the facade method should be ran in a separate thread or not).
However, I have a feeling that there could be a better solution to my problem, whether it be a better way of forcing the method to me synchronous, eg with spring, or by testing the multithreading on some way.
This is where I need your suggestions, how would you solve my problem? Note, I'm using junit for both unit and integration tests.
Simple solution would be to return a Future object like this,
#Async
public Future<String> refreshFoorbar(CallBack cb) {
yourHeavyLifting(); //asynchronous call
return new AsyncResult<String>("yourJobNameMaybe");
}
And in your test, take the future reference and call the get() method.
future.get(); // if its not already complete, waits for it to complete
assertTrue(yourTestCondition)
This blog post shows a sample.
When JUnit testing stuff like this, I use a testing callback with a CountDownLatch that gets counted down by the callback and await()ed by the test method.
private static class TestingCallback implements Callback {
private final CountDownLatch latch;
public TestingCallback(CountDownLatch latch) {
this.latch = latch;
}
#Override public void onEvent() {
this.latch.countDown();
}
}
#Test
public void testCallback() {
final CountDownLatch latch = new CountDownLatch(1);
classUnderTest.execute( new TestCallback(latch) );
assertTrue(latch.await(30, TimeUnit.SECONDS));
}
If the callback is invoked (asynchronously) by the code under test, the latch returns true and the test passes. If the callback doesn't get invoked, the test times out after thirty seconds and the assertion fails.