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
Related
I have some App with WebFlux and i want to use BlockHound, but i need to have a possible turn on and off it through parameter in application.properties or through spring profiling or somthing else.
Also I want to override action, when the lock operation is caught so that not throw error but log warning. And firstly, i did through parameter in application.properties:
#SpringBootApplication
#Slf4j
public class GazPayApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(GazPayApplication.class, args);
BlockHoundSwitch blockHoundSwitch = (BlockHoundSwitch)context.getBean("BlockHoundSwitchBean");
if (blockHoundSwitch.isBlockHoundEnabled()) {
BlockHound.install(builder ->
builder.blockingMethodCallback(it ->
log.warn("find block operation: {}", it.toString())));
}
}
And my BlockHoundSwitch:
#Component("BlockHoundSwitchBean")
#Getter
public class BlockHoundSwitch {
#Value("${blockhound.enabled}")
private boolean blockHoundEnabled;
}
It works for me but in my opinion this solution quite difficult and a little unpredictable.
Next i tried resolve this task through profiling:
#Profile("blockhound_enabled")
#Slf4j
#Component()
public class BlockHoundSwitch {
public BlockHoundSwitch() {
BlockHound.install(builder ->
builder.blockingMethodCallback(it ->
log.warn("find block operation: {}", it.toString())));
}
}
And it works too. Well, I have a few questions:
Which way is better, why and maybe there is another solution?
I need to localize and log, where block operation happened. How can I get class name and method, where it`s happened?
I resolve it. Maybe someone it will be useful. I did it through profiling and my code bellow:
#Profile("blockhound_on")
#Slf4j
#Component()
#Getter
public class BlockHoundSwitch {
public BlockHoundSwitch() {
BlockHound.install(builder ->
builder.blockingMethodCallback(it -> {
List<StackTraceElement> itemList = Arrays.stream(new Exception(it.toString()).getStackTrace())
.filter(i -> i.toString().contains("application.package"))
.collect(Collectors.toList());
log.warn("find block operation: \n{}", itemList);
}));
}
}
where application.package - main package of my project, which i`m finding in stacktrace.
I'm trying to test the following class
#Component
public class StreamListener {
#Value("${kafkaMessageExpiration.maxExpirationInMilliseconds}")
private long messageExpirationTime;
public void handleMessage(Payment payment) {
logIncomingDirectDepositPayment(payment);
if (!isMessageExpired(payment.getLastPublishedDate())) {
messageProcessor.processPayment(payment);
}
}
private boolean isMessageExpired(OffsetDateTime lastPublishedDate) {
return ChronoUnit.MILLIS.between(lastPublishedDate.toInstant(), Instant.now()) > messageExpirationTime;
}
}
I'm getting a "changed conditional boundary → SURVIVED" message on the condition in isMessageExpired().
I have the following tests which test when the difference is less than messageExpirationTime and when the difference is greater than messageExpirationTime.
#BeforeEach
void init() {
ReflectionTestUtils.setField(streamListener, "messageExpirationTime", 60000);
}
#Test
void handleMessage() {
Payment payment = TestObjectBuilder.createPayment();
streamListener.handleMessage(incomingDirectDepositPayment);
verify(messageProcessor).processDirectDepositPayment(incomingDirectDepositPayment);
}
#Test
void handleMessage_expired_message() {
Payment payment = TestObjectBuilder.createPayment();
payment.setLastPublishedDate(OffsetDateTime.now(ZoneId.of("UTC")).minusMinutes(10));
streamListener.handleMessage(incomingDirectDepositPayment);
verify(messageProcessor, never()).processDirectDepositPayment(incomingDirectDepositPayment);
}
I suspect the problem is that I don't have a test where the difference is equal. Assuming that is what I'm missing, I don't know how to get the difference to be equal. Any suggestions on how I can kill this mutation?
BTW, I'm using Java 11, JUnit 5 and PITest 1.6.3
This issue is this
Instant.now()
If time is one of the inputs your code depends on you need to make that explicit so you can control it.
This is normally achieved by injecting a java.util.Clock (held as a field, injected via the constructor). Your call to Instant.now() can then be replaced with clock.instant() and the code becomes properly unit testable.
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.
i don't know if my question was clear, but i am using testNG and i have this:
#Test
public void passengerServiceTest() {
...
}
#AfterTest
public void deleteCreatedPassenger() {
...
}
I want to execute my deleteCreatedPassenger() method after passengerServiceTest, also, i want that in case of deleteCreatedPassenger fails, passengerServiceTest fails too, in other words, i want that both of them be the same test, so if one them fails, test fails.
So i tried with the annotations #AfterTest, #AfterMethod, #AfterClass and all make two tests as "separated" tests.
Do you know how to do this? Regards
You don't need annotations to achieve this, since it's exactly what the finally block is intended for:
#Test
public void passengerServiceTest() {
try {
//test code
} finally {
deleteCreatedPassenger();
}
}
public void deleteCreatedPassenger() {
...
}
If the delete throws an exception then your service test fails.
Annotations are useful in certain scenarios, you shouldn't aim to use them over core language constructs.
alwaysRun
https://testng.org/doc/documentation-main.html
Ex:
#AfterTest(alwaysRun = true)
public void deleteOldValuesFromDatabase() {
...
}
Docs say that this will cause the method to run "even if one or more methods invoked previously failed or was skipped"
OK, so the #Ignore annotation is good for marking that a test case shouldn't be run.
However, sometimes I want to ignore a test based on runtime information. An example might be if I have a concurrency test that needs to be run on a machine with a certain number of cores. If this test were run on a uniprocessor machine, I don't think it would be correct to just pass the test (since it hasn't been run), and it certainly wouldn't be right to fail the test and break the build.
So I want to be able to ignore tests at runtime, as this seems like the right outcome (since the test framework will allow the build to pass but record that the tests weren't run). I'm fairly sure that the annotation won't give me this flexibility, and suspect that I'll need to manually create the test suite for the class in question. However, the documentation doesn't mention anything about this and looking through the API it's also not clear how this would be done programmatically (i.e. how do I programatically create an instance of Test or similar that is equivalent to that created by the #Ignore annotation?).
If anyone has done something similar in the past, or has a bright idea of how else I could go about this, I'd be happy to hear about it.
The JUnit way is to do this at run-time is org.junit.Assume.
#Before
public void beforeMethod() {
org.junit.Assume.assumeTrue(someCondition());
// rest of setup.
}
You can do it in a #Before method or in the test itself, but not in an #After method. If you do it in the test itself, your #Before method will get run. You can also do it within #BeforeClass to prevent class initialization.
An assumption failure causes the test to be ignored.
Edit: To compare with the #RunIf annotation from junit-ext, their sample code would look like this:
#Test
public void calculateTotalSalary() {
assumeThat(Database.connect(), is(notNull()));
//test code below.
}
Not to mention that it is much easier to capture and use the connection from the Database.connect() method this way.
You should checkout Junit-ext project. They have RunIf annotation that performs conditional tests, like:
#Test
#RunIf(DatabaseIsConnected.class)
public void calculateTotalSalary() {
//your code there
}
class DatabaseIsConnected implements Checker {
public boolean satisify() {
return Database.connect() != null;
}
}
[Code sample taken from their tutorial]
In JUnit 4, another option for you may be to create an annotation to denote that the test needs to meet your custom criteria, then extend the default runner with your own and using reflection, base your decision on the custom criteria. It may look something like this:
public class CustomRunner extends BlockJUnit4ClassRunner {
public CTRunner(Class<?> klass) throws initializationError {
super(klass);
}
#Override
protected boolean isIgnored(FrameworkMethod child) {
if(shouldIgnore()) {
return true;
}
return super.isIgnored(child);
}
private boolean shouldIgnore(class) {
/* some custom criteria */
}
}
Additionally to the answer of #tkruse and #Yishai:
I do this way to conditionally skip test methods especially for Parameterized tests, if a test method should only run for some test data records.
public class MyTest {
// get current test method
#Rule public TestName testName = new TestName();
#Before
public void setUp() {
org.junit.Assume.assumeTrue(new Function<String, Boolean>() {
#Override
public Boolean apply(String testMethod) {
if (testMethod.startsWith("testMyMethod")) {
return <some condition>;
}
return true;
}
}.apply(testName.getMethodName()));
... continue setup ...
}
}
A quick note: Assume.assumeTrue(condition) ignores rest of the steps but passes the test.
To fail the test, use org.junit.Assert.fail() inside the conditional statement. Works same like Assume.assumeTrue() but fails the test.