Spring Reactive test doesn't result in an expected error - java

I am writing unit tests for one of our reactive methods. This method depends on another service. I have mocked this service. While running the test, i am getting the below error:
java.lang.AssertionError: expectation "expectError(Class)" failed (expected: onError(MyException); actual: onComplete()).
Here is what I have tried:
Method(ReactiveService.class):
#Autowired
private Service serice;
public Mono<MyObject> save(List<MyObject> arg1, String arg2) {
return SomeUtil.user()
.map(user -> service.save(arg1, arg2, user))
.subscribeOn(scheduler)
.doOnError(e -> {
throw new MyException(MyObject.class, "save object", e);
});
}
Test:
#Mock
Service service;
#InjectMocks
ReactiveService reactiveService;
#Test
public void unit_test(){
when(service.save(any(), any(), any())).thenThrow(new RuntimeException());
Mono<MyObject> result = reactiveService.save(arg1, arg2);
StepVerifier.create(result)
.expectError(MyException.class)
.verify();
}
From the error I understood that, no signal is received from the method. But I am not getting what I have missed here.

I think that the issue you have is caused by SomeUtil.user() not emitting a value.
Why do I think it's the case: I have tried out a simple example to reproduce your issue:
#Test
void test() {
Mono<String> monoWithContent =
Mono.just("Content")
.map(element -> exceptionThrower());
// TEST 1
StepVerifier.create(monoWithContent)
.expectError()
.verify();
Mono<String> monoWithoutContent =
Mono.empty()
.map(element -> exceptionThrower());
// TEST 2
StepVerifier.create(monoWithoutContent)
.expectError()
.verify();
}
String exceptionThrower() {
throw new RuntimeException();
}
The result is :
TEST 1 - passes successfully, because the value is emitted from Mono and it is mapped to an exception (the RuntimeException thrown by the exceptionThrower().
TEST 2 - fails, because there is no value to be emitted from Mono, so the map method is not invoked and the whole execution finishes without an error.
So to summarize, if there is no content emitted from the Publisher, then the transformation operations are not invoked.

Related

Testing CompletableFuture.supplyAsync with Mockito

I am trying to test CompletableFuture.supplyAsync function with mockito but the test is not completing probably because the completable future is not returning. I am not sure what I am missing in the code. Can anyone please help.
I have written the code as follows.
So there are UserService class which returns User, UserEntityService class which returns users entities and a validation class to check if the entities belongs to the user or not.
I want to test if the passed entities belongs to user or not.
class UserService {
CompletableFuture<User> getUser(String userName) {
log.info("Fetching User with username {}", userName);
return CompletableFuture.supplyAsync(
() -> getUserByPortalUserName(userName));
}
}
class UserEntityService {
CompletableFuture<List<UserEntity>> getUserEntities(Long userId) {
log.info("Retrieving all entities for user id {}", userId);
return CompletableFuture.supplyAsync(
() -> getAllByUserId(userId));
}
}
class UserValidationService {
public boolean validateUserCounterparty(UserRequest request)
throws ExecutionException, InterruptedException {
CompletableFuture<Boolean> result = userService.getUser(request.getUserName())
.thenCompose(user -> userEntityService.getUserEntities(user.getUserId()))
.thenCompose(userEntities -> validate(userEntities, request.getUserEntities()));
Boolean validationStatus = result.get();
if (!validationStatus) {
log.error("Validation failed for user name {}", request.getUserName());
}
return validationStatus;
}
}
And the test case is written as
#ExtendWith(MockitoExtension.class)
class UserValidationServiceTest {
#Mock
UserService userService;
#Mock
UserEntityService userEntityService;
#InjectMocks
UserValidationService userValidationService;
#Before
public void init() {
MockitoAnnotations.openMocks(this);
}
#Test
public void validateUser() throws ExecutionException, InterruptedException {
CompletableFuture<User> userFuture = new CompletableFuture<>();
CompletableFuture<List<UserEntity>> userEntityFuture = new CompletableFuture<>();
Mockito.doReturn(userFuture).when(userService).getUser(anyString());
Mockito.doReturn(userEntityFuture).when(userEntityService).getUserEntities(anyLong());
UserRequest request = UserRequest.builder()
.userName("admin")
.userEntities(List.of("US", "ASIA", "EUROPE")).build();
boolean result = validationService.validateUserCounterparty(request);
assertTrue(result);
}
}
On executing this test, it goes into infinite loop and never stops. I guess its because the completable future is not returning but I dont have enough knowledge on how to prevent it.
What modification should I do to prevent it?
In your test method you're creating CompletableFuture instances using new. JavaDoc states:
public CompletableFuture()
Creates a new incomplete CompletableFuture.
So the objects you're creating are never completing, that's why the test is running infinitely. It's not actually a loop, but waiting on a blocking operation to be finished, which never happens.
What you need to do is define a CompletableFuture that completes - immediately or after some time. The simplest way of doing that is by using the static completedFuture() method:
CompletableFuture<User> userFuture =
CompletableFuture.completedFuture(new User());
CompletableFuture<List<UserEntity>> userEntityFuture =
CompletableFuture.completedFuture(List.of(new UserEntity()));
Thanks to that given objects are returned and the code can be executed fully. You can test errors in a similar way by using the failedFuture() method.
I've created a GitHub repo with a minimal reproducible example - the test presented there passes.

Mockito versions behaving differently on a test case

I have fairly simple Test case below which is passing when I use the Mockito 1.19 version but failing when I switched to Mockito 3.x.
#Test(expected = DBMapperRetryableException.class)
public void testUpdateInventoryWithException() throws DBMapperRetryableException, DBMapperNonRetryableException {
when(baseDBOperations.partialUpdate(anyString(), any(PrimaryKey.class), anyString(), anyString(), any(ReturnValue.class), any(ValueMap.class))).thenThrow(new DBMapperRetryableException("Exception", new Throwable()));
assertNull(prebookInventoryDao.updateInventory(PrebookInventoryUpdateInfo.builder().hashKey(DUMMY).sortKey(123).incrementValue(1).build()));
// verify(baseDBOperations, times(1)).partialUpdate(anyString(), any(PrimaryKey.class), anyString(), anyString(), any(ReturnValue.class), any(ValueMap.class));
}
Above shows the below message when I run it on Intellij:
[MockitoHint] PrebookInventoryDaoTest.testUpdateInventoryWithException (see javadoc for MockitoHint):
[MockitoHint] 1. Unused... -> at com.amazon.pbxcoreservice.dao.PrebookInventoryDaoTest.testUpdateInventoryWithException(PrebookInventoryDaoTest.java:68)
[MockitoHint] ...args ok? -> at com.amazon.pbxcoreservice.dao.PrebookInventoryDao.updateInventory(PrebookInventoryDao.java:51)
Error shown: java.lang.AssertionError: Expected exception: com.amazon.pbxcoreservice.dynamodb.exception.DBMapperRetryableException
Here's the PrebookInventoryDao class. I've skipped a few methods like getUpdateExpression, getPrimaryKey.
public class PrebookInventoryDao {
private final BaseDBOperations baseDBOperations;
public PrebookInventoryDao(final BaseDBOperations baseDBOperations) {
this.baseDBOperations = baseDBOperations;
}
public UpdateItemOutcome updateInventory(PrebookInventoryUpdateInfo prebookInventoryUpdateInfo)
throws DBMapperRetryableException, DBMapperNonRetryableException {
ConditionalExpressionRequest conditionalExpressionRequest = prebookInventoryUpdateInfo.getConditionalExpressionRequest();
String conditionalExpression = conditionalExpressionRequest != null ? conditionalExpressionRequest.getQuery() : null;
return baseDBOperations.partialUpdate(DynamoDBConstants.PREBOOK_INVENTORY.TABLE,
getPrimaryKey(prebookInventoryUpdateInfo), getUpdateExpression(),
conditionalExpression, ReturnValue.UPDATED_NEW, getValueMap(prebookInventoryUpdateInfo, conditionalExpressionRequest));
}
Could anyone help as to why such discrepancy?
PS: Looks like when I use when(baseDBOperations.partialUpdate(any(), any(), any(), any(), any(), any())).thenThrow(new DBMapperRetryableException("Exception", new Throwable()));
, then the test passes. Not sure why when(baseDBOperations.partialUpdate(anyString(), any(PrimaryKey.class), anyString(), anyString(), any(ReturnValue.class), any(ValueMap.class))).thenThrow(new DBMapperRetryableException("Exception", new Throwable())); isn't working.

Powermockito : java.lang.IllegalArgumentException: argument type mismatch

I don't have much experience with Mocking, I have recently started using it in my Junit test cases. However, I am having difficulties understanding the execution.
I am getting IllegalArgumentException when I try this code
PowerMockito.doNothing().when(spyObject, "lockUser", String.class, User.class);
But when I provide the values that the lockUser would recieve at the time of execution, everything works as expected.
Working code
PowerMockito.doNothing().when(spyObject, "lockUser", "my_text", userMock);
I am rather confused with this behavior. I was expecting identical behaviour.
Could someone explain why this is happening ?
In addition when I have the following code
PowerMockito.doNothing().when(spyObject, "lockUser", anyString(), anyObject());
The method is no longer mocked and the real method is invoked.
Interestingly I have another method with same name "lockUser" which takes different number of parameters. And in my other test method, I have used only Matchers (anyObject(), anyString() etc) and that works as expected.
PowerMockito.doNothing().when(spyObject, "lockUser", anyObject(), anyString(), anyString(), anyString());
All lockUser methods are priavate.
I am working with Mockito 1.9.5 together with PowerMock 1.5.6
Any help is greatly appreciated
Edit
Additional Code to make it clear
Class Core {
public Worker getWorker(String workerId) {
// Get worker from Map<String, Worker> fID_WRK with workerId as key
// Get user from worker (I have mocked this part, so my mock user is
// returned)
If(user.isTooOld()) {
lockUserAndNotify(reason, user);
throw new UserLockedException("Too old");
}
private void lockUserAndNotify(String reason, User user) {
lockUserAndNotify(reason, user.fname, user.lname); // locks user and notifies
}
public getUser(String login, String password) {
// find user in database
if(user password is too old) {
lockUserAndNotify(dbConnection, fname, lname, userId);
}
}
private lockUserAndNotify(Connection dbConn, String fName, String lName, String
userId) {
//method call to lock the user
//method call to notify the admin
}
}
My Test class
Class CoreTest {
#Test (expected = UserLockedException.class)
public void getUser_ThrowsException() throws
Exception{
Core core = new Core();
Core coreSpy = PowerMockito.spy(core);
when(userMock.isPwdUpdateTimeExpired()).thenReturn(true);
PowerMockito.doNothing().when(coreSpy, "lockUserAndNotify",
anyObject(), anyString(), anyString(), anyString(), anyString());
admin4.UserManager.getUser("l.user1","password");
}
#Test (expected = UserLockedException.class)
public void getWorker_ThrowsException() throws
Exception{
Core core = new Core();
Core coreSpy = PowerMockito.spy(core);
Map workerMap = Whitebox.getInternalState(coreSpy, "fID_WRK");
Map workerMapSpy = PowerMockito.spy(workerMap);
when(workerMapSpy.getWorker("12345")).thenReturn(workerMock);
when(workerMock.getUser()).thenReturn(userMock);
when(userMock.isTooOld()).thenReturn(true);
PowerMockito.doNothing().when(coreSpy, "lockUserAndNotify",
anyString(), anyObject());
admin4.UserManager.getWorker("123445");
}
}
So the test getUser_ThrowsException works as expected, but getWorker_ThrowsException does not.
To answer the part of your question about IllegalArgumentException: argument type mismatch, you get this because you're using the API incorrectly when you use
PowerMockito.doNothing().when(spyObject, "lockUser", String.class, User.class);
See the documentation of PowerMocktioStubber.when, relevant section reproduced here -
public static <T> org.mockito.stubbing.OngoingStubbing<T> when(Class<?> klass,
Object... arguments)
throws Exception
Expect calls to private static methods without having to specify the method name. The method will be looked up using the parameter types if possible
Throws:
Exception - If something unexpected goes wrong.
See Also:
Mockito#when(Object)}
As you've already observed you can use either the values of the real parameters or your can use Matchers like anyString.
Here's some sample code to demonstrate this -
public class Core {
public String getWorker(String workerId) {
if (workerId.isEmpty()) {
lockUser("Reason", workerId);
}
return workerId;
}
private void lockUser(String reason, String user) {
}
}
and the corresponding tests -
#RunWith(PowerMockRunner.class)
#PrepareForTest(Core.class)
public class CoreTest {
#Test
// this is incorrect usage and throws an IllegalArgumentException
public void test1() throws Exception {
Core spy = PowerMockito.spy(new Core());
PowerMockito.doNothing().when(spy, "lockUser", String.class, String.class);
spy.getWorker("");
}
#Test
public void test2() throws Exception {
Core spy = PowerMockito.spy(new Core());
PowerMockito.doNothing().when(spy, "lockUser", Mockito.anyString(), Mockito.anyString());
spy.getWorker("");
PowerMockito.verifyPrivate(spy).invoke("lockUser", Mockito.anyString(), Mockito.anyString());
}
#Test
public void test3() throws Exception {
Core spy = PowerMockito.spy(new Core());
PowerMockito.doNothing().when(spy, "lockUser", "abc", "Reason");
spy.getWorker("abc");
PowerMockito.verifyPrivate(spy, Mockito.times(0)).invoke("lockUser", Mockito.anyString(), Mockito.anyString());
}
}
Without compilable code or the exception that you get for getWorker_ThrowsException, it's not possible to answer why that doesn't work as expected. I can take a look again once you add the required information.

Testing RxJava exception with TestSubscriber

I am trying to write test cases for a function which return some data if validation passes else throws exception
private String validate(Test test) {
//Validation Logic which returns null or throws Exception
}
public Observable<Test> create(Test test) {
return Observable
.just(validate(test))
.flatMap(x -> testRepository
.create(test));
}
Test case for the same
#Test
public void Should_ThrowException_When_NoData() {
Test test = sampleTest();
TestSubscriber<Test> subscriber = new TestSubscriber<>();
testService
.create(test)
.subscribe(subscriber);
subscriber.awaitTerminalEvent();
Throwable thrown = subscriber.getOnErrorEvents().get(0);
assertThat(thrown)
.isInstanceOf(CustomException.class)
.hasFieldOrPropertyWithValue("errorId", 102);
}
But the test case is failing on testService.create itself.
What is the problem here?
Thanks
It fails because you call validate() before its return value is used for creating the Observable. Instead, you can call fromCallable(() -> validate(test)) and get the execution of validate deferred.

Mocking a service that throws an exception?

I want to test a JSF Backing-Bean method "isInProgress" that delegates to a service method "isInProgress". When the service method throws an exception, the bean should put an event on a specific event logger and return false.
When I debug the following test, I get into the catch-block. The mocked service does not throw the exception, but returns a "default answer" which is false for th boolean. What am I doing wrong?
I also wonder if the try-catch around the "when" call can be avoided somehow, as the actual exception is swallowed by the bean under test. In fact I think "declaratively" passing the name of the method to the "when" should suffice. Is there a way to get that cleaner ?
#Test
public void testIsInProgressExeption() {
//prepare object and inputs
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
try {
when(bean.getService().isInProgress()).thenThrow(new Exception());
} catch (Exception e) {
//prepare expected object and result
MyBean expectedBean = new MyBean();
expectedBean.setService(service);
boolean expected = false;
//execute method under test
boolean actual = bean.isInProgress();
//check return values and exceptions
assertEquals(expected, actual);
//check that bean did not change unexpectedly
assertTrue(bean.equals(expectedBean));
//check sideeffects on event log
assertTrue(logEvents.containsMessage("MDI09"));
}
}
For reference here is the updated Test:
#Test
public void testIsInProgressExeption() throws Exception {
//prepare object and inputs
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
when(bean.getService().isInProgress()).thenThrow(new Exception());
//prepare expected object and result
MyBean expectedBean = new MyBean();
expectedBean.setService(service);
boolean expected = false;
//execute method under test
boolean actual = bean.isInProgress();
//check return values and exceptions
assertEquals(expected, actual);
//check that bean did not change unexpectedly
assertTrue(bean.equals(expectedBean));
//check sideeffects on event log
assertTrue(logEvents.containsMessage("MDI09"));
}
Move the when clause out of the try block and change it to:
when(service.isInProgress()).thenThrow(new Exception());
Now it should throw an exception when called.
For the records, I was doing state-base testing. Interestingly, Fowler posted in http://martinfowler.com/articles/mocksArentStubs.html a very nice article that goes quite the same route but then differentiates it from mocking and interaction-based testing.
You are doing it wrong. First you should lay out your test with the BDD or AAA keywords, with BDD :
#Test public void testIsInProgressExeption() {
// given
// when
// then
}
In the given part you will write your fixture, i.e. the setup of your test scenario. In the when part you will call the production code i.e. the tested subject. Lastly in the when part you will write your verifications and or assertions.
Stubs go in the fixture, so this line is misplaced, it doesn't belong here, it' just a definition of the behavior.
when(bean.getService().isInProgress()).thenThrow(new Exception());
However you should directly the service reference instead of the bean.getService(), this is akward.
I don't really understand why you are creating a new instance of the bean in the catch clause, this is weird. But here's how I wold write the test. Note by the way I explain in the unit test name what behavior the test is actully testing, writing this in camel case is way to painful to read, so I use the underscored convention, it's ok in tests.
#Test public void when_service_throw_Exception_InProgress_then_returns_false() throws Exception {
// given
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
when(service.isInProgress()).thenThrow(new Exception());
// when
boolean result = bean.isInProgress();
// then
assertFalse(result);
}
Also I would split the assertion on the event, this a different behavior :
#Test public void when_service_throw_Exception_InProgress_then_log_event_MDI09() throws Exception {
// given
MyBean bean = new MyBean();
MyService service = mock(MyAdapterService.class);
bean.setService(service);
// somehow set up the logEvents collaborator
when(service.isInProgress()).thenThrow(new Exception());
// when
bean.isInProgress();
// then
assertTrue(logEvents.containsMessage("MDI09"));
}
You can even go further to simplify the fixture, if you use JUnit, you write this code :
#RunWith(MockitoJUnitRunner.class)
public class MyBeanTest {
#Mock MyService service;
#Mock LogEvents logEvents;
#InjectMocks MyBean bean;
#Test public void when_service_throw_Exception_InProgress_then_log_event_MDI09() throws Exception {
// given
when(service.isInProgress()).thenThrow(Exception.class);
// when
bean.isInProgress();
// then
verify(logEvents).logEvent("MDI09");
}
}
In the example above I also extrapolated on the log event stuff, but it is just to give the idea of what's possible.

Categories