I need to test if a lambda function is called n-times from a service instance.
I have a Service class, that interact with the repository, when an error occur on retriving data from repository the service should retry until a max number of retries is reached so I have implemented as follow:
interface Repository {
Collection<String> getData();
}
public class RetryHelper<T> {
private Integer retries;
public RetryHelper(Integer retries) {
this.retries = retries;
}
public interface Operation<T> {
T doIt() throws Exception;
}
public T doWithRetry(Operation<T> operation) throws Exception {
int remainRetries = retries;
do {
try {
return operation.doIt();
} catch (Exception e) {
if (remainRetries == 0) {
throw e;
}
//TODO: wait before retry
remainRetries--;
}
} while (true);
}
}
class Service {
#Inject
Repository repo;
private final RetryHelper<Collection<String>> retryHelper;
public Collection<String> callService() {
try {
Collection<String> res = retryHelper.doWithRetry(() ->
repo.getData());
return res;
} catch (Exception e) {
throw (CustomException) e;
}
}
}
I need to test using Mockito that repo.getData() is called n-times when error occurs. I can change the Service code and the RetryHelper, so I am open to suggestions.
I have try to implment the test following tutorials and documentations:
public class ServiceTest {
#Inject
Service service;
#InjectMock
Repository repository;
#InjectMock
RetryHelper<Collection<String>> retryHelper;
#Captor
ArgumentCaptor<RetryHelper.Operation<Collection<String>>> operation;
#BeforeEach
void init_mocks() {
MockitoAnnotations.openMocks(this);
}
#Test
void shouldRetryIfDataQueryFailsForNonFatalError() throws Exception {
when(repository.getData())
.thenThrow(new RuntimeException("Runtime Exception"));
service.callService();
verify(retryHelper).doWithRetry(operation.capture());
verify(repository, times(2)).getData();
}
}
The test fail with message that getData() is never called.
I have finally found the solution without using Captor
public class ServiceTest {
#Inject
Service service;
#InjectMock
Repository repository;
#Inject
RetryHelper<Collection<String>> retryHelper;
#Test
void shouldRetryIfDataQueryFailsForNonFatalError() throws Exception {
when(repository.getData())
.thenThrow(new RuntimeException("Runtime Exception"));
try {
service.callService();
} catch(Exception e) {
verify(repository, times(2)).getData();
}
}
}
Related
I am trying to write Junit test cases for a void method. This method is used for updating values in Database. I have tried certain test cases and its returning a success. But when I check the coverage its showing zero. Can anyone tell me the proper way to write test cases for void methods.
this is my service class :
public class CustomerServiceImpl implements CustomerService {
#Autowired
ERepository eRepository;
#Autowired
ActivityUtil activityUtil;
#Override
public void updateCustomer(RequestDTO requestDTO)
throws CustomAException {
if (Objects.nonNull(requestDTO.getAdmissionId())) {
Optional<Admission> optionalAdmission = eRepository.findById(
requestDTO.getAdmissionId());
if (optionalAdmission .isPresent()) {
EAdmission eAdmission = optionalAdmission.get();
updateCustomer(requestDTO, eAdmission);
} else {
throw new CustomAException ("Admission details not found");
}
}
else {
throw new CustomAException ("Admission id not found");
}
}
private void updateCustomer(RequestDTO requestDTO,
EAdmission eAdmission)
throws CustomAException {
logger.info("updating customer info");
try {
if (ObjectUtils.isNotEmpty(eAdmission.getCustomer())) {
eAdmission.getCustomer().setCustomerEmailAddress(
requestDTO.getEmail());
eAdmission.getCustomer().setCorporateTelephoneNumber(
requestDTO.getCustomerPhone());
eAdmission.getCustomer().setZipCode(requestDTO.getZipCode());
eAdmission.getCustomer().setCustomerAddress1(requestDTO.getAddress1());
evfAdmissionRepository.save(evfAdmission);
activityUtil.createActivityLog(eAdmission, Constants.ENTRY_COMPLETED);
} else {
throw new CustomAException ("Customer details not found ");
}
} catch (Exception exception) {
logger.error(Constants.CUSTOMER_UPDATE_ERROR_MESSAGE);
throw new CustomAException (Constants.CUSTOMER_UPDATE_ERROR_MESSAGE);
}
I am trying to write test cases for updateCustomer but my test class has zero coverage even though its a success.
test class :
#SpringBootTest
public class CustomerServiceImplTest {
#InjectMocks
CustomerServiceImpl CustomerServiceImpl;
#Mock
ERepository eRepository ;
#Mock
ActivityUtil activityUtil;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void updateCustomerException() throws Exception {
CustomerServiceImpl CustomerServiceImplTest = mock(CustomerServiceImpl.class);
when(evfAdmissionRepository.findById(any())).thenThrow(ConstraintViolationException.class);
Mockito.doThrow(CustomAException .class).when(CustomerServiceImplTest).updateCustomer(setRequestDTO());
}
#Test
public void updateCustomerSuccess() throws Exception {
CustomerServiceImpl CustomerServiceImplTest = mock(CustomerServiceImpl.class);
CustomerServiceImplTest .updateCustomer(setRequestDTO());
//doNothing().when(evfCustomerServiceImpl).updateEVFCustomerOnSubscribe(any());
verify(CustomerServiceImplTest ).updateCustomerOn(setRequestDTO());
}
private RequestDTO setRequestDTO() {
RequestDTO eRequestDTO = new RequestDTO ();
eRequestDTO .setEmail("test");
// rest of code for setting value
return eRequestDTO ;
}
ArgumentCaptor can be used to capture and Assert Arguments in your method. you can read about ArgumentCaptor here
Is it possible to test methods of serviceA and tell the test environment to replace all calls from serviceA to serviceB with a mockServiceB?
This is the method I´d like to test (ServiceA.java):
#Inject
ServiceB serviceB;
public boolean tokenExists(ItemModel item) throws Exception {
try {
List<ItemModel > items = serviceB.getItems();
String token = item.getToken();
return items.stream().filter(i -> i.getToken().equals(token)).findFirst().isPresent();
} catch (ApiException e) {
throw new Exception(500, "Data could not be fetched.", e);
}
}
Since serviceB.getItems() will result in a REST-call, I'd like to replace calls to serviceB with a custom mockServiceB where I just load mock data from a json file like this (TestServiceA.java):
#InjectMock
ServiceB serviceB;
#ApplicationScoped
public static class ServiceB {
private ObjectMapper objMapper = new ObjectMapper();;
public List<ItemModel> getItems() throws IOException {
final String itemsAsJson;
try {
itemsAsJson= IOUtils.toString(new FileReader(RESOURCE_PATH + "items.json"));
objMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
List<Item> items= objMapper.readValue(itemsAsJson, objMapper.getTypeFactory().constructCollectionType(List.class, ItemModel.class));
return items;
} catch (IOException e) {
throw e;
}
}
}
And then test the tokenExists method like this (TestServiceA.java):
#Test
public void testTokenExists() {
try {
Assertions.assertTrue(this.serviceA.tokenExists(this.getTestItem()));
} catch (Exception e) {
fail("exception");
}
}
However, when I run the test testTokenExists it still calls the original serviceB.getItems(). So my question would be if this is even possible or if I have to take a different approach.
You need to mock the serviceB inside ServiceA class, you have two different options:
First
You can use whitebox or reflection and assign the mocked the serviceB to ServiceA class
public class TestExample {
#Mock
private ServiceB mockedServiceB;
private ServiceA serviceA;
#Before
public void setUp() {
serviceA = new ServiceA();
}
#Test
public void shouldDoSomething() {
// Arrange
Whitebox.setInternalState(serviceA, "serviceB", mockedServiceB);
when(serviceB.getItems()).thenReturn(list);
// Act & Assert
Assertions.assertTrue(serviceA.tokenExists(this.getTestItem()));
}
}
Second
You can use #InjectMocks
public class TestExample {
#Mock
private ServiceB mockedServiceB;
#InjectMocks
private ServiceA serviceA;
#Before
public void setUp() {
serviceA = new ServiceA();
}
#Test
public void shouldDoSomething() {
// Arrange
List<Item> list = new ArrayList<>();
list.add(new Item(...)); // add whatever you want to the list
when(serviceB.getItems()).thenReturn(list);
// Act & Assert
Assertions.assertTrue(serviceA.tokenExists(this.getTestItem()));
}
}
Note: prepare your runner also
I have #transactional method that seems to be working (rolling back) if run the actual service and provide inputs that would cause a run-time error. If I create a Test for that method that throws a run-time error it doesn't seem to rollback anymore. Any idea why this doesn't work while testing?
it's somthing like:
#Service
public class SampleServiceImpl implements SampleService {
private final RepoA repoA;
private final RepoB repoB;
public SampleServiceImpl(RepoA repoA, RepoB repoB) {
this.repoA = repoA,
this.repoB = repoB
}
#Transactional
#Override
public void addItems() {
repoA.save(new ItemA(1,'name1')); //works
repoB.save(new ItemB(2,'name2')); //throws run-time error
}
}
#RunWith(SpringRunner.class)
#DataJpaTest
public class Tests {
#Autowired
private RepoA repoA;
#Mock
private Repob repoBMock;
#Test
public void whenExceptionOccurrs_thenRollsBack() {
var service = new SampleService(repoA, repoBMock);
Mockito.when(repoBMock.save(any(ItemB.class))).thenThrow(new RuntimeException());
boolean exceptionThrown = false;
try {
service.addItems()
} catch (Exception e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
Assert.assertFalse(repoA.existsByName('name1')); // this assertion fails because the the first item still exists in db
}
}
Just add annotation Rollback and set the flag to false.
#Test
#Rollback(false)
I make my telegram bot using java library TelegramBots and also use SpringBoot in my application.
When in class TelegramBotPolling there is called method onUpdateReceived than the field busStationBtnsGenerator is null.
How corectly autowire field busStationBtnsGenerator in order it will be not null when onUpdateReceived method is called ?
This is brief example of my code:
#Component
public class TelegramBotPolling extends TelegramLongPollingBot {
#Autowired
BusStationBtnsGenerator busStationBtnsGenerator;
static {
ApiContextInitializer.init();
}
#PostConstruct
public void registerBot(){
TelegramBotsApi telegramBotsApi = new TelegramBotsApi();
try {
telegramBotsApi.registerBot(new TelegramBotPolling());
} catch (TelegramApiException e) {
logger.error(e);
}
}
#Override
public void onUpdateReceived(Update update) {
// When this method is called field "busStationBtnsGenerator" is null.
}
}
#Component
public class BusStationBtnsGeneratorImpl implements BusStationBtnsGenerator {
#Autowired
BusStationsParser stationsParser;
#Autowired
UrlHelper urlHelper;
#Override
public InlineKeyboardMarkup getKeyboardMarkupForBusStations()
throws Exception
{
......
}
private List<List<InlineKeyboardButton>> getBusStationButtons()
throws Exception
{
.....
}
}
Instance of class created with constructor new is not managed by Spring. For referring it you should you this keyword in this case.
#PostConstruct
public void registerBot(){
TelegramBotsApi telegramBotsApi = new TelegramBotsApi();
try {
telegramBotsApi.registerBot(this);
} catch (TelegramApiException e) {
logger.error(e);
}
In my test when I assert de exception message I'm getting null
I'm not getting mock the message inside Service.... :(
I have:
My test:
#RunWith(MockitoJUnitRunner.class)
public class ServiceImplTest {
#InjectMocks
private ServiceImpl service;
#Mock
private Message message;
public static final String REQUIRED_FIELD = "required.field";
#Before
public void setUp() {
when(message.getMessage(eq(REQUIRED_FIELD), any(List.class))).thenReturn(REQUIRED_FIELD);
System.out.println(message.getMessage(REQUIRED_FIELD, new ArrayList()));
}
#Test(expected = MyException.class)
public void testCancel_shouldValidCancellation_and_ThrowTicketException_with_RequiredFieldMessage() {
try {
Object object = //... new object
service.do(object);
} catch (Exception e) {
assertEquals(REQUIRED_FIELD, e.getMessage()); // <-- e.getMessage return null!!!
}
}
}
My service:
#Service
public class ServiceImpl implements Service {
#Autowired
Message message;
#Override
public Object do(Object object) {
if(object.getSomeAttribute() == null)) {
throw new MyException(message.getMessage("required.field", "attribute"));
}
//doSomething...
return something;
}
}
In the setUp() of test the printLn() prints required.field but I can't use message of Service!!
Can someone help me?
It is hard tell for sure, without knowledge about the interface of Message, but it is easy to spot that you configure mock object to stub method with signature getMessage(String, List):
when(message.getMessage(eq(REQUIRED_FIELD), any(List.class))).thenReturn(REQUIRED_FIELD);
However, ServiceImpl uses getMessage(String, String). The default value which is returned by mock in this case is null. You have to configure mock object properly.