Testing CompletableFuture.supplyAsync with Mockito - java

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.

Related

How to create mock test to soft-delete void method?

I would like to test my delete method which looks like:
public void deleteUser(String id) {
var userEntity = userRepository.findById(Integer.valueOf(id))
.orElseThrow(() -> new UserNotFoundException("Id not found"));
if (userEntity.getLastAccessDate() == null) {
throw new ProhibitedAccessException("Policy has been violated");
}
userRepository.delete(userEntity);
}
My delete method in repository is the following:
#Modifying
#Query("update UserEntity u set deleted = true where u = :userEntity")
void delete(UserEntity userEntity);
And I've written the following test:
#Test
void deleteUserTest(){
final int id = 1;
UserEntity userEntity = new UserEntity();
var idString = String.valueOf(id);
when(userRepository.findById(id)).thenReturn(Optional.of(userEntity));
assertThrows(RuntimeException.class, () -> userService.deleteUser(idString));
}
This test is working good but it didn't cover the
userRepository.delete(userEntity);
Could you help me please - how can I add it to my test? Previously, I've tried to to do it through verify but it didn't help.
Test coverage means, which lines of your code are being called. If you mock an object, you are not calling the real code but only simulate the behaviour
Your only test the implementation of your userService and mock the behaviour of your userRepository.
So your test only covers the code inside of your userService.deleteUser(...) method, but not the code inside of your userRepository.
If you want to cover your userRepository, you have to write a test with a 'real' userRepository.

How to test a service with two dependencies

I don't know how to test a service with Mockito if it has two dependencies and the second dependency should work with the result of the first.
To better describe my problem, I wrote a small application for this: https://github.com/MartinHein-dev/mockito-example
With http://localhost:8080/countries one gets the result of three countries from https://restcountries.com/
I would be very happy if you could show me how the unit tests for de.example.mockito.service.CountryService.class would look like.
It feels wrong to continue with the mocked result of this.restCountiesClient.findCountriesByCode(countryCodes) and use it as a parameter in this.countryMapper.map(restCountryList), whose result is also mocked.
#ExtendWith(MockitoExtension.class)
class CountryServiceTest {
#Mock
RestCountriesClient client;
#Mock
CountryMapper mapper;
CountryService countryService;
List<RestCountry> restCountryList;
List<CountryDto> countryDtoList;
final String COUNTRY_CODES = "pe,at";
#BeforeEach
void setUp() throws Exception {
countryService = new CountryService(client, mapper);
restCountryList = List.of(
new RestCountry(new RestCountryName("Peru", "Republic of Peru")),
new RestCountry(new RestCountryName("Austria", "Republic of Austria"))
);
countryDtoList = List.of(
new CountryDto("Peru", "Republic of Peru"),
new CountryDto("Austria", "Republic of Austria")
);
}
#Test
void getAllCountries() {
given(client.findCountriesByCode(COUNTRY_CODES)).willReturn(restCountryList);
given(mapper.map(restCountryList)).willReturn(countryDtoList);
List<CountryDto> result = this.countryService.getAllCountries(COUNTRY_CODES);
assertEquals(2, result.size());
assertEquals("Peru", result.get(0).getCommonName());
assertEquals("Republic of Peru", result.get(0).getOfficialName());
assertEquals("Austria", result.get(1).getCommonName());
assertEquals("Republic of Austria", result.get(1).getOfficialName());
}
#Test
void getAllCountries2() {
given(client.findCountriesByCode(COUNTRY_CODES)).willReturn(restCountryList);
given(mapper.map(restCountryList)).willReturn(countryDtoList);
List<CountryDto> result = this.countryService.getAllCountries2(COUNTRY_CODES);
assertEquals(2, result.size());
assertEquals("Peru", result.get(0).getCommonName());
assertEquals("Republic of Peru", result.get(0).getOfficialName());
assertEquals("Austria", result.get(1).getCommonName());
assertEquals("Republic of Austria", result.get(1).getOfficialName());
}
#AfterEach
void tearDown() throws Exception {
reset(client, mapper);
}
Updated tests (2):
#Test
void getAllCountries() {
given(client.findCountriesByCode(COUNTRY_CODES)).willReturn(restCountryList);
given(mapper.map(restCountryList)).willReturn(countryDtoList);
this.countryService.getAllCountries(COUNTRY_CODES);
verify(client, times(1)).findCountriesByCode(COUNTRY_CODES);
verify(mapper, times(1)).map(restCountryList);
}
#Test
void getAllCountries2() {
given(client.findCountriesByCode(COUNTRY_CODES)).willReturn(restCountryList);
given(mapper.map(restCountryList)).willReturn(countryDtoList);
List<CountryDto> result = this.countryService.getAllCountries2(COUNTRY_CODES);
assertEquals("Other Name", restCountryList.get(0).getName().getCommon());
verify(client, times(1)).findCountriesByCode(COUNTRY_CODES);
verify(mapper, times(1)).map(restCountryList);
}
When you are testing getAllCountries you are testing that the data flow works within the method, because the method itself doesn't do anything else but pass data between the dependencies. Therefore you do not need to set up populated objects for that method and you do not need to assert that the returned objects contain any particular populated data. You only need to verify that the expected (mocked) dependencies were called with the expected object references.
Testing the values you set up in th test belong to the unit test that target CountryMapper.
For getAllCountries2 you would have to verify that the value in the test data has changed like you expect, but again no need to verify the values otherwise.

Junit Test: findById method of the Repository

I am new in Junit tests and I have a question about it. Here you can see the method findById in my service class:
#Service
public class DefaultQuarterService implements QuarterService {
private final QuarterRepository quarterRepository;
public DefaultQuarterService(QuarterRepository quarterRepository) {
this.quarterRepository = quarterRepository;
}
#Override
public QuarterEntity findById(int id) {
return quarterRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(String.format("Quarter does not exist for id = %s!", id)));
}
}
And here is my QuarterRepository:
#Repository
public interface QuarterRepository extends CrudRepository<QuarterEntity, Integer> {
}
And here is my Junit implementation for this method:
#MockBean
private QuarterRepository quarterRepository;
#Test
public void throwExceptionWhenQuarterIdNotFound() {
int id = anyInt();
when(quarterRepository.findById(id))
.thenReturn(Optional.empty());
assertThatAnExceptionWasThrown(String.format("Quarter does not exist for id = %s!", id));
}
public void assertThatAnExceptionWasThrown(
String errorMsg
) {
expectException.expect(RuntimeException.class);
expectException.expectMessage(errorMsg);
}
Unfortunately test doesn't pass. Here the error in terminal:
java.lang.AssertionError: Expected test to throw (an instance of
java.lang.RuntimeException and exception with message a string
containing "Quarter does not exist for id = 0!")
Maybe it is so simple but I can not see what I am missing. I would be so happy if you can direct me. Thanks a lot!
As you mock your Repository it will return with Optional.empty() correctly, I think you should call your service's (which is Autowired) findById method. It will throw the exception actually.
First issue
In the assertThatAnExceptionWasThrown method you expect RuntimeException BUT in the service class you throw EntityNotFoundException, So I guess you should expect EntityNotFoundException in your test case.
Second issue
After this part of the code.
when(quarterRepository.findById(id))
.thenReturn(Optional.empty());
Why didn't you call your service method (findById)?
When you are returning the empty value, you should verify your condition with the service method you want to test it.
It should be something like this.
assertThatThrownBy(() -> defaultQuarterService.findById(id))
.isInstanceOf(ApiRequestException.class)
.hasMessageContaining("PUT_YOUR_EXCEPTION_MESSAGE_HERE");
This is a good sample for unit-test in the spring boot. You can check it out. Link
Try the above solutions and let me know it has been fixed or not. Good luck

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.

Categories