I am not able to test a method that is inside an abstract class instance. I have already tried several ways and would like to know if it is possible to do this. The contents of the abstract class can be seen in the link below.
Jacoco Class Report
Belows is the JUnit and Mockito test that I did trying to test the cases in the image above.
#RunWith(MockitoJUnitRunner.class)
public class PahoRxMqttCallbackTest {
#Test
public void whenConnectionLostOccurs() {
PahoRxMqttCallback rxMqttCallback = mock(PahoRxMqttCallback.class);
assertThat(rxMqttCallback).isNotNull();
PahoRxMqttException exception = new PahoRxMqttException(
new MqttException(MqttException.REASON_CODE_CONNECTION_LOST));
ArgumentCaptor<Throwable> onConnectionLostCauseArgumentCaptor = ArgumentCaptor.forClass(Throwable.class);
rxMqttCallback.connectionLost(exception);
verify(rxMqttCallback).connectionLost(onConnectionLostCauseArgumentCaptor.capture());
assertThat(onConnectionLostCauseArgumentCaptor.getValue()).isNotNull();
assertThat(onConnectionLostCauseArgumentCaptor.getValue()).isInstanceOf(PahoRxMqttException.class);
assertThat(onConnectionLostCauseArgumentCaptor.getValue()).hasCauseInstanceOf(MqttException.class);
assertThat(onConnectionLostCauseArgumentCaptor.getValue()).isEqualTo(exception);
}
#Test
public void whenConnectCompleteOccurs() {
PahoRxMqttCallback rxMqttCallback = mock(PahoRxMqttCallback.class);
assertThat(rxMqttCallback).isNotNull();
boolean reconnect = true;
String brokerUri = "tcp://localhost:1883";
ArgumentCaptor<Boolean> onConnectCompleteReconnectArgumentCaptor = ArgumentCaptor.forClass(Boolean.class);
ArgumentCaptor<String> onConnectCompleteServerUriArgumentCaptor = ArgumentCaptor.forClass(String.class);
rxMqttCallback.connectComplete(reconnect, brokerUri);
verify(rxMqttCallback).connectComplete(
onConnectCompleteReconnectArgumentCaptor.capture(),
onConnectCompleteServerUriArgumentCaptor.capture());
assertThat(onConnectCompleteReconnectArgumentCaptor.getValue()).isNotNull();
assertThat(onConnectCompleteReconnectArgumentCaptor.getValue()).isEqualTo(reconnect);
assertThat(onConnectCompleteServerUriArgumentCaptor.getValue()).isNotNull();
assertThat(onConnectCompleteServerUriArgumentCaptor.getValue()).isEqualTo(brokerUri);
}
#Test
public void whenDeliveryCompleteOccurs() {
PahoRxMqttCallback rxMqttCallback = mock(PahoRxMqttCallback.class);
assertThat(rxMqttCallback).isNotNull();
IMqttDeliveryToken deliveryToken = mock(IMqttDeliveryToken.class);
assertThat(deliveryToken).isNotNull();
RxMqttToken rxMqttToken = new PahoRxMqttToken(deliveryToken);
//ArgumentCaptor<IMqttDeliveryToken> onDeliveryCompleteTokenArgumentCaptor = ArgumentCaptor.forClass(IMqttDeliveryToken.class);
ArgumentCaptor<RxMqttToken> onDeliveryCompleteRxTokenArgumentCaptor = ArgumentCaptor.forClass(RxMqttToken.class);
//rxMqttCallback.deliveryComplete(deliveryToken);
rxMqttCallback.deliveryComplete(rxMqttToken);
/*
* Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
* Mocking methods declared on non-public parent classes is not supported.
*/
//verify(rxMqttCallback).deliveryComplete(onDeliveryCompleteTokenArgumentCaptor.capture());
verify(rxMqttCallback).deliveryComplete(onDeliveryCompleteRxTokenArgumentCaptor.capture());
//assertThat(onDeliveryCompleteTokenArgumentCaptor.getValue()).isNotNull();
//assertThat(onDeliveryCompleteTokenArgumentCaptor.getValue()).isExactlyInstanceOf(IMqttDeliveryToken.class);
//assertThat(onDeliveryCompleteTokenArgumentCaptor.getValue()).isEqualTo(deliveryToken);
assertThat(onDeliveryCompleteRxTokenArgumentCaptor.getValue()).isNotNull();
assertThat(onDeliveryCompleteRxTokenArgumentCaptor.getValue()).isExactlyInstanceOf(PahoRxMqttToken.class);
assertThat(onDeliveryCompleteRxTokenArgumentCaptor.getValue()).isEqualTo(rxMqttToken);
}
//#Test
public void whenMessageArrived() throws Exception {
PahoRxMqttCallback rxMqttCallback = mock(PahoRxMqttCallback.class);
assertThat(rxMqttCallback).isNotNull();
String topic = "topic";
MqttMessage message = new MqttMessage();
ArgumentCaptor<String> onMessageArrivedTopicArgumentCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<MqttMessage> onMessageArrivedMessageArgumentCaptor = ArgumentCaptor.forClass(MqttMessage.class);
rxMqttCallback.messageArrived(topic, message);
/*
* Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
* Mocking methods declared on non-public parent classes is not supported.
*/
verify(rxMqttCallback).messageArrived(onMessageArrivedTopicArgumentCaptor.capture(), onMessageArrivedMessageArgumentCaptor.capture());
assertThat(onMessageArrivedTopicArgumentCaptor.getValue()).isNotNull();
assertThat(onMessageArrivedTopicArgumentCaptor.getValue()).isEqualTo(topic);
assertThat(onMessageArrivedMessageArgumentCaptor.getValue()).isNotNull();
assertThat(onMessageArrivedMessageArgumentCaptor.getValue()).isEqualTo(message);
}
}
I really could not do it even after searching the web about it. So I appreciate the help.
Update
I was able to perform the tests and cover all the alerts that the Jacoco had shown. But for this I had to create an implementation for the abstract class rather than using anonymous class. As can be seen in the following link
Jacoco Class Report 2
The updated unit tests:
#RunWith(MockitoJUnitRunner.class)
public class PahoRxMqttCallbackTest {
#Test
public void whenConnectionLostOccurs() {
PahoRxMqttCallback rxMqttCallback = spy(PahoRxMqttCallback.create(cause -> {}, (recon, uri) -> {}, t -> {}));
PahoRxMqttException exception = new PahoRxMqttException(
new MqttException(MqttException.REASON_CODE_CONNECTION_LOST));
ArgumentCaptor<Throwable> onConnectionLostCauseArgumentCaptor = ArgumentCaptor.forClass(Throwable.class);
rxMqttCallback.connectionLost(exception);
verify(rxMqttCallback).connectionLost(onConnectionLostCauseArgumentCaptor.capture());
assertThat(onConnectionLostCauseArgumentCaptor.getValue()).isNotNull();
assertThat(onConnectionLostCauseArgumentCaptor.getValue()).isInstanceOf(PahoRxMqttException.class);
assertThat(onConnectionLostCauseArgumentCaptor.getValue()).hasCauseInstanceOf(MqttException.class);
assertThat(onConnectionLostCauseArgumentCaptor.getValue()).isEqualTo(exception);
}
#Test
public void whenConnectCompleteOccurs() {
PahoRxMqttCallback rxMqttCallback = spy(PahoRxMqttCallback.create(cause -> {}, (r, u) -> {}, t -> {}));
boolean reconnect = true;
String brokerUri = "tcp://localhost:1883";
ArgumentCaptor<Boolean> onConnectCompleteReconnectArgumentCaptor = ArgumentCaptor.forClass(Boolean.class);
ArgumentCaptor<String> onConnectCompleteServerUriArgumentCaptor = ArgumentCaptor.forClass(String.class);
rxMqttCallback.connectComplete(reconnect, brokerUri);
verify(rxMqttCallback).connectComplete(
onConnectCompleteReconnectArgumentCaptor.capture(),
onConnectCompleteServerUriArgumentCaptor.capture());
assertThat(onConnectCompleteReconnectArgumentCaptor.getValue()).isNotNull();
assertThat(onConnectCompleteReconnectArgumentCaptor.getValue()).isEqualTo(reconnect);
assertThat(onConnectCompleteServerUriArgumentCaptor.getValue()).isNotNull();
assertThat(onConnectCompleteServerUriArgumentCaptor.getValue()).isEqualTo(brokerUri);
}
#Test
public void whenDeliveryCompleteOccurs() {
PahoRxMqttCallback rxMqttCallback = spy(PahoRxMqttCallback.create(cause -> {}, (r, u) -> {}));
IMqttDeliveryToken deliveryToken = new MqttDeliveryToken();
RxMqttToken rxMqttToken = new PahoRxMqttToken(deliveryToken);
ArgumentCaptor<IMqttDeliveryToken> onDeliveryCompleteTokenArgumentCaptor = ArgumentCaptor.forClass(IMqttDeliveryToken.class);
ArgumentCaptor<RxMqttToken> onDeliveryCompleteRxTokenArgumentCaptor = ArgumentCaptor.forClass(RxMqttToken.class);
rxMqttCallback.deliveryComplete(deliveryToken);
rxMqttCallback.deliveryComplete(rxMqttToken);
verify(rxMqttCallback).deliveryComplete(onDeliveryCompleteTokenArgumentCaptor.capture());
verify(rxMqttCallback, times(2)).deliveryComplete(onDeliveryCompleteRxTokenArgumentCaptor.capture());
assertThat(onDeliveryCompleteTokenArgumentCaptor.getValue()).isNotNull();
assertThat(onDeliveryCompleteTokenArgumentCaptor.getValue()).isExactlyInstanceOf(MqttDeliveryToken.class);
assertThat(onDeliveryCompleteTokenArgumentCaptor.getValue()).isEqualTo(deliveryToken);
assertThat(onDeliveryCompleteRxTokenArgumentCaptor.getValue()).isNotNull();
assertThat(onDeliveryCompleteRxTokenArgumentCaptor.getValue()).isExactlyInstanceOf(PahoRxMqttToken.class);
assertThat(onDeliveryCompleteRxTokenArgumentCaptor.getValue()).isEqualTo(rxMqttToken);
}
#Test
public void whenMessageArrived() throws Exception {
PahoRxMqttCallback rxMqttCallback = spy(PahoRxMqttCallback.create(cause -> {}, (r, u) -> {}, t -> {}));
String topic = "topic";
MqttMessage message = new MqttMessage();
ArgumentCaptor<String> onMessageArrivedTopicArgumentCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<MqttMessage> onMessageArrivedMessageArgumentCaptor = ArgumentCaptor.forClass(MqttMessage.class);
rxMqttCallback.messageArrived(topic, message);
verify(rxMqttCallback).messageArrived(onMessageArrivedTopicArgumentCaptor.capture(), onMessageArrivedMessageArgumentCaptor.capture());
assertThat(onMessageArrivedTopicArgumentCaptor.getValue()).isNotNull();
assertThat(onMessageArrivedTopicArgumentCaptor.getValue()).isEqualTo(topic);
assertThat(onMessageArrivedMessageArgumentCaptor.getValue()).isNotNull();
assertThat(onMessageArrivedMessageArgumentCaptor.getValue()).isEqualTo(message);
}
}
You never instantiate the class, only mocks of the class.
PahoRxMqttCallback rxMqttCallback = mock(PahoRxMqttCallback.class);
A mock is not the real class, just a fake copy.
Instead you should do
PahoRxMqttCallback rxMqttCallback = new PahoRxMqttCallback();
or
PahoRxMqttCallback rxMqttCallback = spy(new PahoRxMqttCallback());
Related
Trying to test repository method, but my test fails with following "Wanted but not invoked: cellphonesDao.deleteAllCellphones();"
Here is repo method:
#Override
public Single<Cellphone[]> getCellphones() {
Single<CellPhoneEntity[]> remoteCellphones =
networkModule.productApi()
.getCellPhones()
.onErrorResumeNext(cellphonesDao.getAllCellphones()); // todo return value if true
Single<CellPhoneEntity[]> localCellphones = cellphonesDao.getAllCellphones();
return Single.zip(remoteCellphones, localCellphones, (remote, local) -> {
if (!Arrays.equals(remote, local)) {
cellphonesDao.deleteAllCellphones();
for (CellPhoneEntity cellPhoneEntity : remote) {
cellphonesDao.insertCellphone(cellPhoneEntity);
}
}
return mapper.toCellphones(remote);
});
}
Main porpuse is to test repo method in correct way. Guess the way I chose is not good.
Here is test implementation:
class CellPhoneRepositoryImplTest {
NetworkModule networkModule;
CellphonesDao cellphonesDao;
CellphoneMapper cellphoneMapper;
CellPhoneRepositoryImpl cellPhoneRepository;
ProductAPI productAPI;
#BeforeEach
void setUp() {
networkModule = Mockito.mock(NetworkModule.class);
cellphonesDao = Mockito.mock(CellphonesDao.class);
productAPI = Mockito.mock(ProductAPI.class);
cellphoneMapper = new CellphoneMapper();
cellPhoneRepository = Mockito.spy(new CellPhoneRepositoryImpl(
networkModule,
cellphonesDao,
cellphoneMapper
));
}
#Test
void whenRemoteDataAreDifferentFromLocalDbIsUpdated() {
int numberOfCellphones = 5;
CellPhoneEntity[] remoteCellphones = DummyCellphoneEntityFactory.generateCellphones(numberOfCellphones);
CellPhoneEntity[] localCellphones = DummyCellphoneEntityFactory.generateCellphones(numberOfCellphones);
Mockito.when(networkModule.productApi()).thenReturn(productAPI);
Mockito.when(networkModule.productApi().getCellPhones()).thenReturn(wrapWithSingle(remoteCellphones));
// Mockito.when(networkModule.productApi().getCellPhones().onErrorResumeNext(cellphonesDao.getAllCellphones())).thenReturn(wrapWithSingle(remoteCellphones));
Mockito.when(cellphonesDao.getAllCellphones()).thenReturn(wrapWithSingle(localCellphones));
Mockito.doNothing().when(cellphonesDao).deleteAllCellphones();
cellPhoneRepository.getCellphones();
Mockito.verify(cellphonesDao, Mockito.times(1))
.deleteAllCellphones();
}
private Single<CellPhoneEntity[]> wrapWithSingle(CellPhoneEntity[] cellphones) {
return Single.just(cellphones);
}
}
I will be glad for any suggestion)
The code inside the returned Single isn't executed immediately, but your verifications are. Try calling cellPhoneRepository.getCellphones().blockingGet() instead of just cellPhoneRepository.getCellphones(). The blockingGet() should make your test wait until the Single is done executing.
I am new to Junit 5 . There are two functions in the class under test , The first function calls the second function and second function returns a value which is used in the first function for processing .
So I have created a mock for this class but not able to mock the second function call When I am testing the first function .
First function --exportOpportunityListing()
Second function -- entityToCsvReport()
public class OpportunityReportServiceImpl extends BaseService implements OpportunityReportService {
#Value("${nfs.mountPath}")
private String fileMountPath;
#Value("${take1.url.host}")
private String take1HostURL;
#Autowired
UsersRepository usersRepository;
#Autowired
MailUtil mailUtil;
#Autowired
OpportunityJDBCRepository ojdbc;
#Override
#Async
public void exportOpportunityListing(Map<String, Object> paramMap, List<OpportunityCriteria> lfvo,
String xRemoteUser) {
try {
List<OpportunityJDBCDTO> lo = ojdbc.getOppListWithoutPagination(paramMap, lfvo);
List<OpportunityReport> exportData = lo.parallelStream().map(this::entityToCsvReport)
.collect(Collectors.toList());
CsvCustomMappingStrategy<OpportunityReport> mappingStrategy = new CsvCustomMappingStrategy<>();
mappingStrategy.setType(OpportunityReport.class);
String dirPath = fileMountPath + REPORT_PATH;
File fileDir = new File(dirPath);
if (!fileDir.exists()) {
FileUtils.forceMkdir(fileDir);
}
String pathWithoutExtension = dirPath + "opportunity_data_"
+ LocalDateTime.now().format(DateTimeFormatter.ofPattern(YYYYMMDDHHMMSS));
File reportFile = new File(pathWithoutExtension + EXTENSION_CSV);
Writer writer = new PrintWriter(reportFile);
StatefulBeanToCsv<OpportunityReport> beanToCsv = new StatefulBeanToCsvBuilder<OpportunityReport>(writer)
.withMappingStrategy(mappingStrategy).build();
beanToCsv.write(exportData);
writer.close();
String zipFilePath = pathWithoutExtension + EXTENSION_ZIP;
ZipUtil.zip(reportFile, zipFilePath);
Users remoteUser = usersRepository.findByUsername(xRemoteUser)
.orElseThrow(() -> new Take1Exception(ErrorMessage.USER_NOT_FOUND_WITH_USERNAME, xRemoteUser));
Mail mail = Mail.builder().to(new String[] { remoteUser.getEmail() })
.model(MailModel.builder().name(remoteUser.getName())
.body("Please find attached the opportunity report you requested.").build())
.subject("Opportunity Report").attachments(Arrays.asList(new File(zipFilePath))).build();
mailUtil.sendMail(mail);
Files.delete(reportFile.toPath());
} catch (IOException | CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
throw new Take1Exception(ErrorMessage.INTERNAL_SERVER_EXCEPTION, e);
}
}
public OpportunityReport entityToCsvReport(OpportunityJDBCDTO o) {
OpportunityReport or = modelMapper.map(o, OpportunityReport.class);
or.setCurrency("USD");
or.setOnline(Boolean.TRUE.equals(o.getIsOnline()) ? "YES" : "NO");
return or;
}
}
Here is my JUnit Test case .
class OpportunityReportServiceImplTest {
#InjectMocks
OpportunityReportServiceImpl opportunityReportServiceImpl;
#Autowired
OpportunityReportServiceImpl ors;
#Mock
OpportunityJDBCRepository ojdbc;
#Mock
UsersRepository usersRepository;
#Mock
MailUtil mailUtil;
#Mock
ModelMapper mp;
String username = "anandabhishe";
String nfusername = "ananda";
Mail mail;
List<OpportunityJDBCDTO> lo = new ArrayList<OpportunityJDBCDTO>();
List<OpportunityReport> lor = new ArrayList<OpportunityReport>();
#BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(opportunityReportServiceImpl, "fileMountPath", ".");
ReflectionTestUtils.setField(opportunityReportServiceImpl, "take1HostURL", "");
lo.add(new OpportunityJDBCDTO());
lor.add(new OpportunityReport());
}
#Test
void testExportOpportunityListing() throws IOException {
OpportunityReport or = new OpportunityReport();
or.setCurrency("USD");
or.setOnline("Yes");
when(ojdbc.getOppListWithoutPagination(getParamMap(), getOppCriteria())).thenReturn(lo);
when(usersRepository.findByUsername(username)).thenReturn(Optional.of(getUser()));
doNothing().when(mailUtil).sendMail(mail);
// doNothing().when(opportunityReportServiceImpl).entityToCsvReport(oj);
when(opportunityReportServiceImpl.entityToCsvReport(getOpportunityJDBCDTO())).thenReturn(or);
opportunityReportServiceImpl.exportOpportunityListing(getParamMap(), getOppCriteria(), username);
assertTrue(true);
FileUtils.forceDelete(new File("." + REPORT_PATH));
}
private Map<String, Object> getParamMap() {
return new HashMap<String, Object>();
}
private List<OpportunityCriteria> getOppCriteria() {
List<OpportunityCriteria> loc = new ArrayList<>();
loc.add(new OpportunityCriteria());
return loc;
}
private OpportunityJDBCDTO getOpportunityJDBCDTO() {
OpportunityJDBCDTO oj = new OpportunityJDBCDTO();
oj.setIsOnline(true);
oj.setApplicationCount(2);
oj.setCost(200);
oj.setCountryCode("in");
oj.setCreationDate(LocalDateTime.now());
oj.setEndDate(LocalDate.now());
oj.setLocation("test");
oj.setOpportunityId(123);
oj.setOpportunityStatus("test");
oj.setOpportunityStatusId(1);
oj.setOpportunityTitle("test");
oj.setOpportunityType("test");
oj.setOpportunityTypeColor("test");
oj.setOpportunityTypeId(1);
oj.setPublishedAt(LocalDateTime.now());
oj.setPublishedBy("test");
oj.setPublishedByUserName("test");
oj.setRegistrationUrl("test");
oj.setStartDate(LocalDate.now());
oj.setSummary("test");
oj.setUserEmail("test");
oj.setUserFullName("test");
oj.setUserId(1);
oj.setUserName("test");
oj.setVendorName("test");
return oj;
}
private Users getUser() {
Users user = new Users();
return user;
}
}
I am getting Null Pointer Exception when the line in Test class is called :
when(opportunityReportServiceImpl.entityToCsvReport(getOpportunityJDBCDTO())).thenReturn(or);
I was missing mocking the modelmapper stub which is being used in second function , after I added that , the test passed .
OpportunityReport or = new OpportunityReport();
OpportunityJDBCDTO oj = new OpportunityJDBCDTO();
when(ojdbc.getOppListWithoutPagination(any(HashMap.class), anyList())).thenReturn(lo);
when(usersRepository.findByUsername(anyString())).thenReturn(Optional.of(getUser()));
doNothing().when(mailUtil).sendMail(mail);
doReturn(or).when(mp).map(oj, OpportunityReport.class);
opportunityReportServiceImpl.exportOpportunityListing(getParamMap(), getOppCriteria(), username);
assertTrue(true);
That's happening because opportunityReportServiceImpl is not a mock - it's the object that you're trying to test, but you're trying to stub a method of it as if it were a mock.
I would recommend that you don't try to stub the methods of the object that you're trying to test. But if you have to, you'll need to declare it as a #Spy. Then to stub it, you'll need the doReturn/when syntax instead of when/thenReturn. This might look like
doReturn(lo).when(ojdbc).getOppListWithoutPagination(getParamMap(), getOppCriteria());
I am using the akka framework with its Java API and mockito + Testkit for unit testing the actor
Here is the actor
public class K8sDeploymentCreator extends AbstractActor {
private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
#Override
public Receive createReceive() {
return receiveBuilder().match(createK8sDeployment.class, msg -> {
KubeNamespace kubenamespace = new KubeNamespace();
KubeDeployment kubeDeployment = new KubeDeployment();
Namespace namespace = kubenamespace.createNamespace(msg.kubeClient, msg.service);
Deployment deployment = kubeDeployment.createDeployment(msg.service, msg.kubeClient, namespace);
log.info("sending complete depl msg");
getSender().tell(new K8sDeploymentComplete(deployment), getSelf());
})
.matchAny(o -> log.info("received unknown message")).build();
}
}
And here is the test class
public class K8sDeploymentCreatorTest extends JUnitSuite {
static ActorSystem system;
#Before
public void setup() {
system = ActorSystem.create();
KubeDeployment mockKubeDeployment = mock(KubeDeployment.class);
KubeNamespace mockKubeNamespace = mock(KubeNamespace.class);
Deployment deployment = Mockito.mock(Deployment.class);
Namespace namespace = Mockito.mock(Namespace.class);
KubernetesClient kubeClient = Mockito.mock(KubernetesClient.class);
Service serviceTodeploy = new Service("group","artifact","version");
DeployEnvironment deployEnvironment = new DeployEnvironment();
deployEnvironment.setName("K8sDeploymentCreatorTest");
serviceTodeploy.setDeployEnvironment(deployEnvironment);
when(mockKubeNamespace.createNamespace(kubeClient, serviceTodeploy)).thenReturn(namespace);
when(mockKubeDeployment.createDeployment(serviceTodeploy, kubeClient, namespace)).thenReturn(deployment);
}
#AfterClass
public static void teardown() {
TestKit.shutdownActorSystem(system);
system = null;
}
#Test
public void testK8sDeployment() {
new TestKit(system) {
{
final Props props = Props.create(K8sDeploymentCreator.class);
final ActorRef underTest = system.actorOf(props);
KubeDeployment mockKubeDeployment = mock(KubeDeployment.class);
KubeNamespace mockKubeNamespace = mock(KubeNamespace.class);
Deployment deployment = Mockito.mock(Deployment.class);
Namespace namespace = Mockito.mock(Namespace.class);
KubernetesClient kubeClient = Mockito.mock(KubernetesClient.class);
DeployEnvironment deployEnvironment = new DeployEnvironment();
deployEnvironment.setName("K8sDeploymentCreatorTest");
Service serviceTodeploy = new Service("group","artifact","version");
serviceTodeploy.setDeployEnvironment(deployEnvironment);
createK8sDeployment msg = new createK8sDeployment(serviceTodeploy, kubeClient);
underTest.tell(msg, getRef());
expectMsg(K8sDeploymentComplete)
}
};
}
}
This fails with a NPE (NullPointerException) trying to execute code inside createNamespace(). This method has been mocked, should it skip the excution and just return whatever the when statement says it should return?
Is this because I am instantiating a new objec of KubeNamspace and also KubeDeployment where as the contact is for mocks?
You are not actually mocking anything in your test. You are creating mock objects but they are not getting injected into the code under test. Your actor is executing the following code on response to a message:
KubeNamespace kubeNamespace = new KubeNamespace();
KubeDeployment kubeDeployment = new KubeDeployment();
This creates new un-mocked objects which will run their course as coded -- and often result in NPEs since they don't have the external dependencies they rely upon.
If you want to mock objects that are created this way you either have to refactor your code to extract the creation of them into a mockable factory class or use a more invasive mock library such as PowerMock or jMockit.
Example of Factory mock
class KubeFactory {
public KubeNamespace makeNamespace() {
return new KubeNamespace();
}
public KubeDeployment makeDeployment() {
return new KubeDeployment();
}
}
public class K8sDeploymentCreator extends AbstractActor {
private final KubeFactory factory;
K8sDeploymentCreator() {
this(new KubeFactory());
}
// This constructor allows you to override the factory used for testing
K8sDeploymentCreator(KubeFactory factory) {
this.factory = factory;
}
#Override
public Receive createReceive() {
return receiveBuilder().match(createK8sDeployment.class, msg -> {
KubeNamespace kubenamespace = factory.makeNamespace();
KubeDeployment kubeDeployment = factory.makeDeployment();
// rest is as before...
});
}
}
Then in your test class you create a test KubeFactory which returns mocked instances for the classes you are testing with:
#Test
public void testK8sDeployment() {
new TestKit(system) {
{
final KubeFactory mockFactory = mock(KubeFactory.class);
when(mockFactory.makeNamespace()).thenReturn(mockKubeNamespace);
when(mockFactory.makeDeployment()).thenReturn(mockKubeDeployment);
final Props props = Props.create(K8sDeploymentCreator.class, mockFactory);
final ActorRef underTest = system.actorOf(props);
// and so on...
}
}
}
I have been converting some code to be asynchronous. The original unit test used the annotation #Test(expected = MyExcpetion.class) but I don't think this will work because the exception I want to assert on is wrapped in java.util.concurrent.ExcutionException . I did try calling my future like this but my assertion is still failing and I don't love that I had to add in return null
myApiCall.get(123).exceptionally((ex) -> {
assertEquals(ex.getCause(),MyCustomException.class)
return null
}
I also tried this flavor but still not working
myApiCall.get(123).exceptionally((ex) -> {
assertThat(ex.getCause())
.isInstanceOF(MyException.class)
.hasMessage("expected message etc")
return null;
}
My API just throws exception if it can't find id. How should I be properly testing this? Can I use that original annotation in anyway?
my api call reaches out to db when run. In this test I am setting up my future to return an error so it doesn't actually try to communicate with anything. the code under test looks like this
public class myApiCall {
public completableFuture get(final String id){
return myService.getFromDB(id)
.thenApply(
//code here looks at result and if happy path then returns it after
//doing some transformation
//otherwise it throws exception
)
}
}
in the unit test I force myService.getFromDB(id) to return bad data so I can test exception and also keep this a unit test don't reach out to db etc.
Let's assume your API throws if called with 0:
public static CompletableFuture<Integer> apiCall(int id) {
return CompletableFuture.supplyAsync(() -> {
if (id == 0) throw new RuntimeException("Please not 0!!");
else return id;
});
}
You can test that it works as expected with the following code (I'm using TestNG but I suspect it won't be too difficult to translate into a JUnit test):
#Test public void test_ok() throws Exception {
CompletableFuture<Integer> result = apiCall(1);
assertEquals(result.get(), (Integer) 1);
}
#Test(expectedExceptions = ExecutionException.class,
expectedExceptionsMessageRegExp = ".*RuntimeException.*Please not 0!!")
public void test_ex() throws Throwable {
CompletableFuture<Integer> result = apiCall(0);
result.get();
}
Note that the second test uses the fact that the ExecutionException message will contain the original exception type and message and captures the expectation with a regex. If you can't do that with JUnit, you can call result.get() in a try/catch block and call throw e.getCause(); in the catch block. In other words, something like this:
#Test(expectedExceptions = RuntimeException.class,
expectedExceptionsMessageRegExp = "Please not 0!!")
public void test_ex() throws Throwable {
CompletableFuture<Integer> result = apiCall(0);
try {
result.get();
} catch (ExecutionException e) {
throw e.getCause();
}
}
You can try also alternative option:
import org.hamcrest.core.IsInstanceOf;
import org.junit.rules.ExpectedException;
public class Test() {
#Rule
public ExpectedException thrown = ExpectedException.none();
#Test
public void myApiCallTest() {
thrown.expect(ExcutionException.class);
thrown.expectCause(IsInstanceOf.instanceOf(MyException.class));
thrown.expectMessage("the message you expected");
myApiCall.get("");
}
}
Assuming that:
public class myApiCall {
public completableFuture get(final String id) {
// ...
throw new ExcutionException(new MyException("the message you expected"))
}
}
Assume that you have a class and you want to test a method which returns a completable future:
public class A {
private final Api api;
public A(Api api) { this.api = api;}
public CompletableFuture<Void> execute(Integer input) {
final CompletableFuture<Void> future = api.execute(input)
.thenApplyAsync(result -> doSomething())
.exceptionally(ex -> doFailure());
return future;
}
}
To test the execution of "doSomething()" then you may use mockito and do the following:
// prepare test
final Api api = mock(Api.class)
final A a = new A(api);
when(api.execute(any(Integer.class)))
.thenReturn(CompletableFuture.completedFuture(null));
// execute
final CompletableFuture<Void> result = a.execute(input);
// validate
...
To test "doFailure" do the following:
when(api.execute(any(Integer.class))).thenAnswer(answer -> {
CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(new RuntimeException());
return future;
});
// execute
final CompletableFuture<Void> result = a.execute(input);
// validate
assertTrue(result.isCompletedExceptionally());
that is easy thing doing in junit-4. Are you remember the #RunWith annotation? Yes, write your own TestRunner to intercept the exception before the junit expected exception processor is invoked, for example:
public class ConcurrentRunner extends BlockJUnit4ClassRunner {
public ConcurrentRunner(Class<?> klass) throws InitializationError {
super(klass);
}
#Override
protected Statement possiblyExpectingExceptions(FrameworkMethod method,
Object test,
Statement next) {
return super.possiblyExpectingExceptions(
method, test, throwingActualException(next)
);
}
private Statement throwingActualException(Statement next) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
try {
next.evaluate();
} catch (ExecutionException | CompletionException source) {
throw theActualExceptionOf(source);
}
}
private Throwable theActualExceptionOf(Exception source) {
return source.getCause() != null ? source.getCause() : source;
}
};
}
}
just annotated with #RunWith(ConcurrentRunner.class) on the test, you needn't change your test code at all. for example:
#RunWith(ConcurrentRunner.class)
public class ConcurrentExpectedExceptionTest {
#Test(expected = IllegalArgumentException.class)
public void caughtTheActualException() throws Throwable {
myApiCall().join();
}
private CompletableFuture<Object> myApiCall() {
return CompletableFuture.supplyAsync(() -> {
throw new IllegalArgumentException();
});
}
}
I've got the following unit test, which works almost fine:
#Test(expectedExceptions = {IllegalArgumentException.class}, expectedExceptionsMessageRegExp =
PasswordValidator.INVALID_PASSWORD)
public void testInvalidPasswordsThrowException() throws Exception {
for (String invalidPassword: getInvalidPasswords()){
new LaxPasswordValidator(invalidPassword);
}
}
private String[] getInvalidPasswords() {
return new String[] {INVALID_PASSWORD_1, INVALID_PASSWORD_2, INVALID_PASSWORD_3, INVALID_PASSWORD_4,
INVALID_PASSWORD_5, INVALID_PASSWORD_6, INVALID_PASSWORD_7, INVALID_PASSWORD_8, INVALID_PASSWORD_9,
INVALID_PASSWORD_10};
}
As you can see, it is just asserting that new LaxPasswordValidator(invalidPassword) works as expected and throws an exception.
Problem: It is just taking INVALID_PASSWORD_1 into account, so it stops in the first iteration; it launches the exception but does not continue checking the other passwords. How can I make it test them all? Thanks
You must annotate the method as #DataProvider
#DataProvider(name = "invalid-passwords")
public Object[][] getInvalidPasswords() {
return new String[][]{
{"INVALID_PASSWORD_1"},
{"INVALID_PASSWORD_2"},
{"INVALID_PASSWORD_3"}
};
}
and annotate the #Test method to use this dataprovider for the parameters.
#Test(expectedExceptions = {IllegalArgumentException.class},
expectedExceptionsMessageRegExp = PasswordValidator.INVALID_PASSWORD,
dataProvider = "invalid-passwords")
public void testInvalidPasswords(String password) throws Exception {
new LaxPasswordValidator(password);
}