How to cover a private method in JUnit Testing - java

Please help me how am I going to cover the privated method in my class used in a public method. Whenever I run my JUnit coverage, it says that that private method has a missing branch.
Here is the code that uses that private method:
public String addRecord(Record rec) throws IOException {
GeoPoint geoPoint = locationService.getLocation(rec.getTerminalId());
if (Objects.isNull(geoPoint)) {
loggingService.log(this.getClass().toString(), rec.getTerminalId(), "GET LOCATION",
"No Coordinates found for terminal ID: " + rec.getTerminalId());
return "No Coordinates found for terminal ID: " + rec.getTerminalId();
}
loggingService.log(this.getClass().toString(), rec.getTerminalId(), "GeoPoint",
"Latitude: " + geoPoint.getLat() + " Longitude: " + geoPoint.getLon());
format(rec);
loggingService.log(this.getClass().toString(), rec.getTerminalId(), "addRecord",
"Formatted Payload" + rec.toString());
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject().field("terminalId", rec.getTerminalId())
.field("status", "D".equals(rec.getStatus()) ? 1 : 0).field("recLocation", rec.getLocation())
.field("errorDescription", rec.getErrorDescription()).field("lastTranTime", rec.getLastTranTime())
.field("lastDevStatTime", rec.getLastDevStatTime()).field("errorCode", rec.getErrorCode())
.field("termBrcode", rec.getTermBrcode()).timeField("#timestamp", new Date())
.latlon("location", geoPoint.getLat(), geoPoint.getLon()).endObject();
IndexRequest indexRequest = new IndexRequest(prop.getEsIndex(), prop.getEsType(), rec.getTerminalId())
.source(builder);
IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT);
loggingService.log(this.getClass().toString(), rec.getTerminalId(), TraceLog.SUCCESSFUL_PUSH_TO_ELASTIC_SEARCH,
util.mapToJsonString(rec));
return response.getResult().name();
}
This is the the private method:
private Record format(Record rec) {
if (rec.getLocation() == null) {
rec.setLocation("");
}
if (rec.getTermBrcode() == null) {
rec.setTermBrcode("");
}
if (rec.getErrorDescription() == null) {
rec.setErrorDescription("");
}
return rec;
}
This is my Junit code:
#Before
public void setUp() throws ParseException, IOException {
client = mock(RestHighLevelClient.class);
indexRequest = mock(IndexRequest.class);
indexResponse = mock(IndexResponse.class);
MockitoAnnotations.initMocks(this);
rec= new Record();
rec.setLocation("location");
rec.setStatus("U");
rec.setErrorCode("222");
rec.setErrorDescription("STATUS");
rec.setLastDevStatTime("02-02-2020");
rec.setLastTranTime("02-02-2020");
rec.setTerminalId("123");
rec.setTermBrcode("111");
ReflectionTestUtils.setField(client, "client", restClient);
}
#Test
public void testAddRecordIsNull()
throws IOException, NumberFormatException, IllegalArgumentException, IllegalAccessException {
Mockito.when(locationService.getLocation(Mockito.anyString())).thenReturn(null);
elasticsearchService.addRecord(rec);
assertThat(1).isEqualTo(1);
}
#Test
public void testFormat() throws IOException {
rec = new Record();
rec.setLocation(null);
rec.setStatus(null);
rec.setErrorCode(null);
rec.setErrorDescription(null);
rec.setLastDevStatTime(null);
rec.setLastTranTime(null);
rec.setTerminalId(null);
rec.setTermBrcode(null);
elasticsearchService.addRecord(rec);
//ReflectionTestUtils.invokeMethod(ElasticsearchService.class, "addAtmStatusRecord", rec);
Mockito.when(elasticsearchService.addRecord(null)).thenReturn("");
//elasticsearchService.addRecord(atm);
//Mockito.when(locationService.getLocation(Mockito.anyString())).thenReturn(atm);
//elasticsearchService.addRecord(null);
assertThat(1).isEqualTo(1);
}
Please help me on where am I missing on my JUnit to cover the private method 'format'. Any help will be much appreciated. Thanks.

In testFormat, if elasticsearchService.addRecord is being tested, it shouldn't mocked. i.e. Remove Mockito.when(elasticsearchService.addRecord(null)).thenReturn("");
What should be mocked are the services / dependencies used in the method. e.g. loggingService
xxx
Update #1: EclEmma is telling you that the body of the if statements are red. This means that testAddRecordIsNull is not configured correctly. It is passing a Record object that has values. Instead of passing rec, pass new Record(). This assumes that the attributes of the new Record has default values of null. If you need to have a Record that has values for other attributes, create a new record accordingly.

Yes! I finally found the solution.
#Test
public void testFormat() throws IOException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
rec= new Record();
rec.setLocation(null);
rec.setStatus(null);
rec.setErrorCode(null);
rec.setErrorDescription(null);
rec.setLastDevStatTime(null);
rec.setLastTranTime(null);
rec.setTerminalId(null);
rec.setTermBrcode(null);
java.lang.reflect.Method method = ElasticsearchService.class.getDeclaredMethod("format", Record.class);
method.setAccessible(true);
Record output = (Record) method.invoke(es, rec);
assertEquals(output, rec);
}
Reflection is the key. Sharing it here so others running on the same issue might have this for assistance. Thanks.

Related

Problem with mocking - Null Pointer Exception when trying to mock external API method

I'm trying to test the class:
public class WeatherProvider {
private OWM owm;
public WeatherProvider(OWM owm) {
this.owm = owm;
}
public CurrentWeatherData getCurrentWeather(int cityId) throws APIException, UnknownHostException {
CurrentWeatherData currentWeather = new CurrentWeatherData(owm.currentWeatherByCityId(cityId));
return currentWeather;
}
public HourlyWeatherForecastData getHourlyWeather(int cityId) throws APIException, UnknownHostException {
HourlyWeatherForecastData hourlyWeather = new HourlyWeatherForecastData(owm.hourlyWeatherForecastByCityId(cityId));
return hourlyWeather;
}
}
OWM is an external API so I want to mock it. I wrote a test method:
#Test
void shouldReturnCurrentWeather() throws APIException, UnknownHostException {
//given
OWM owm = mock(OWM.class);
WeatherProvider weatherProvider = new WeatherProvider(owm);
int cityId = 123;
CurrentWeather currentWeather = WeatherDataStub.getCurrentWeather();
given(owm.currentWeatherByCityId(cityId)).willReturn(currentWeather);
//when
CurrentWeatherData currentWeatherData = weatherProvider.getCurrentWeather(cityId);
//then
}
I get a java.lang.NullPointerException in given().willReturn() line and I don't know why. I want to test cases when owm.currentWeatherByCityId(cityId) succesfuly returns CurrentWeather object (which is a stub class in this case) or throws exceptions.
Can you explain to me what I do wrong?

Java NegativeTest private method with Reflection to catch custom ApplicationException

So I'm testing a AccountService class with a mocked databaselayer.
In this AccountService class there is a private method that checks the input received from UI according to a regex.
The positive test I wrote is working fine:
#Test
public void testEmailPatroonCorrect() throws Exception{
//Correcte emails
List<String> emails = new ArrayList<>();
emails.add("user#domain.com");
emails.add("user#domain.co.in");
emails.add("user.name#domain.com");
emails.add("user_name#domain.com");
emails.add("username#yahoo.corporate.in");
Class<AccountService> foo = AccountService.class;
Method method = foo.getDeclaredMethod("checkEmailPatroon", String.class);
method.setAccessible(true);
assertThatCode(() -> {
for(String email : emails){
method.invoke(AccountService,email);
}}).doesNotThrowAnyException();
}
However for the negative test (a list with wrong email patterns) even with only one object in the list for simplicity
#Test
public void testEmailPatroonFout() throws Exception{
//Verkeerde emailpatronen
List<String> emails = new ArrayList<>();
emails.add(".username#yahoo.com");
Class<AccountService> foo = AccountService.class;
Method method = foo.getDeclaredMethod("checkEmailPatroon", String.class);
method.setAccessible(true);
assertThatThrownBy(()->{
for(String email : emails){
method.invoke(AccountService,email);
}
}).isInstanceOf(ApplicationException.class).hasMessage(ApplicationExceptionType.ONGELDIGE_EMAIL.getMsg());
}
The exception thrown during test is: java.lang.reflect.InvocationTargetException. In the application the ApplicationException gets caught just fine.
Question is how can I write a proper test for a list of wrong email patterns? (without using #VisibleForTesting functionality since it's a school project).
Many thanks!
The InvocationTargetException wraps the exception thrown within the reflectively invoked method. So you may catch the InvocationTargetException and rethrow its cause, but I’d put that into a utility method, like
public interface TestMethod<D,A> {
void invoke(D d, A a) throws Throwable;
}
static <D,A> TestMethod<D,A> method(
Class<D> declarer, String name, Class<A> argType) throws ReflectiveOperationException {
Method method = declarer.getDeclaredMethod(name, argType);
method.setAccessible(true);
return (d,a) -> {
try {
method.invoke(d, a);
} catch(InvocationTargetException ex) {
throw ex.getTargetException();
}
};
}
which you can use like
#Test
public void testEmailPatroonFout() throws Exception{
//Verkeerde emailpatronen
List<String> emails = new ArrayList<>();
emails.add(".username#yahoo.com");
TestMethod<AccountService, String> method
= method(AccountService.class, "checkEmailPatroon", String.class);
assertThatThrownBy(() -> {
for(String email : emails){
method.invoke(AccountService, email);
}
}).isInstanceOf(ApplicationException.class)
.hasMessage(ApplicationExceptionType.ONGELDIGE_EMAIL.getMsg());
}
The shape of the TestMethod interface allows the alternative implementation like
static <D,A> TestMethod<D,A> method(
Class<D> declarer, String name, Class<A> argType) throws ReflectiveOperationException {
Method method = declarer.getDeclaredMethod(name, argType);
method.setAccessible(true);
return MethodHandleProxies.asInterfaceInstance(
TestMethod.class, MethodHandles.lookup().unreflect(method));
}
Thanks to Holger I was able to write a working test for it's purpose.
#Test
public void testEmailPatroonFoutLoop() throws Throwable {
//Verkeerde emailpatronen
List<String> wrongEmails = new ArrayList<>();
wrongEmails.add(".username#yahoo.com");
wrongEmails.add("username#yahoo.com.");
wrongEmails.add("usernameyahoo.com");
wrongEmails.add("username#yahoo.c");
wrongEmails.add("use..rname#yahoo.com");
Class<AccountService> foo = AccountService.class;
Method method = foo.getDeclaredMethod("checkEmailPatroon", String.class);
method.setAccessible(true);
int countedWrongEmails = 0;
for(String email : wrongEmails){
try{
method.invoke(accServ,email);
}
catch (InvocationTargetException ie){
Exception e = (Exception) ie.getTargetException();
if(e.getMessage().equals(ApplicationExceptionType.ONGELDIGE_EMAIL.getMsg())){
countedWrongEmails++;
}
}
}
assertThat(countedWrongEmails).isEqualTo(wrongEmails.size());
}
Although I see the benefits and elegance of writing a TestMethod interface, I however do not yet possess the knowledge to grasp it's complexity. So I will stick to this test that I'll be able to explain on the verbal exam.

How to test a method using a PrintWriter?

I have following method:
#Component
public class WriteCsvToResponse {
private static final Logger LOGGER = LoggerFactory.getLogger(WriteCsvToResponse.class);
public void writeStatus(PrintWriter writer, Status status) {
try {
ColumnPositionMappingStrategy mapStrategy
= new ColumnPositionMappingStrategy();
mapStrategy.setType(Status.class);
String[] columns = new String[]{"id", "storeId", "status"};
mapStrategy.setColumnMapping(columns);
StatefulBeanToCsv btcsv = new StatefulBeanToCsvBuilder(writer)
.withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
.withMappingStrategy(mapStrategy)
.withSeparator(',')
.build();
btcsv.write(status);
} catch (CsvException ex) {
LOGGER.error("Error mapping Bean to CSV", ex);
}
}
I have no idea how to test it properly using mockito.
Use it to wrap the object status into csv format.
I used StringWriter to wrap the response in it.
There are no more details left, but it seems I have to create some words to pass the validation :)
You do not need mockito to test this method, only a java.io.StringWriter.
Here is how you can write the test for a nominal use:
#Test
void status_written_in_csv_format() {
// Setup
WriteCsvToResponse objectUnderTest = new WriteCsvToResponse ();
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
// Given
Status status = ...
// When
objectUnderTest.writeStatus(printWriter, status);
// Then
String actualCsv = stringWriter.toString();
assertThat(actualCsv.split("\n"))
.as("Produced CSV")
.containsExactly(
"id,storeId,status",
"42,142,OK");
}
This example assume some things about your Status object, but you have the general idea.
For assertions, I use AssertJ, but you can do the same with JUnit5 built-in assertions.
Hope this helps !
With a bit of a refactoring, where the Builder is a Spring Bean injected into this component.
You can then mock that builder to return a mocked StatefulBeanToCsv, specifically the write method, where you write the conditions and assertions. If you encounter an error, you throw some unchecked exception, like IllegalStateException, if everything is alright, you don't throw anything
you can write a test like this and change your input in write method:
#Test
public void test() {
WriteCsvToResponse writeCsvToResponse = mock(WriteCsvToResponse.class);
doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
write((Status)args[1]);
return null;
}
}).when(writeCsvToResponse).writeStatus(any(PrintWriter.class),any(Status.class));
writeCsvToResponse.writeStatus(writer, status);
}
public void write(Status status) {
// do anythings you like with status
}

Random Mockito Stubbing exception

I have the following code snippet from a unit test using Mockito which has happily been passing for months/years.
#Test
public void testAddRemoveTarFile() throws IOException, GeneralSecurityException {
//add a TAR file
TableRowCount downloadRowCount = new TableRowCount(tableDownloads);
//create the item that will appear in the table row
MyHSAItem item = createMyHSAItem();
Mockito.when(model.getConfigurations(Type.TAR)).thenReturn(Arrays.asList(item));
//get the table model
JTable downloadsTable = (JTable)UI.findComponent(getPanel(), "download");
final MyHSATableModel tableModel = (MyHSATableModel ) downloadsTable.getModel();
final MyHSAEvent event = Mockito.mock(MyHSAEvent.class);
Mockito.when(event.getType()).thenReturn(MyHSAEvent.Type.MODEL);
//Fire table event when adding observation
final File xmlFile = Mockito.mock(File.class);
Mockito.doAnswer(new Answer<Void>() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
tableModel.modelChanged(event);
return null;
}
}).when(model).addObservation(xmlFile);
//Fire table event when deleting observation
Mockito.doAnswer(new Answer<Void>() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
tableModel.modelChanged(event);
return null;
}
}).when(model).delete(item, true);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
UI.findButtonWithText(getPanel(), "Add ...").doClick();
}
});
//select a file, table row should update
chooseFile(xmlFile);
ensureEquals(1, downloadRowCount, TIMEOUT);
// Remove download + cancel
UI.leftClick(tableDownloads);
clickRemove("Cancel");
ensureEquals(1, downloadRowCount, TIMEOUT);
// Remove download + OK
UI.leftClick(tableDownloads);
Mockito.when(model.getConfigurations(Type.TAR)).thenReturn(new ArrayList<MyHSAItem>());
clickRemove("OK");
ensureEquals(0, downloadRowCount, TIMEOUT);
}
Suddenly it failed just once with:
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at herschel.ia.pal.pool.hsa.gui.MyHsaPreferencePanelTest.testAddRemoveTarFile(MyHsaPreferencePanelTest.java:257)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. although stubbed methods may return mocks, you cannot inline mock creation (mock()) call inside a thenReturn method (see issue 53)
I understand this error but not how it can randomly happen. The Mockito.doAnswer seems to be the problem. I am not inlining mocks and it seems to be ok and has always worked. What can it be?
Line 257 stars with
Mockito.doAnswer(new Answer<Void>() {
model is indeed a field initialised like so:
#Mock
private MyHSANotifiableModelImpl model;
public void setUpPanel() {
MockitoAnnotations.initMocks(this);
Both the answers return null and have signature Void, so not sure what you mean exactly.
Thanks for any help

Suggestion for improving cli wrapper over Apache Commons CLI

I have following cli wrapper:
public class CLIUtil
{
// some private variable and other methods
public CLIUtil(final String[] args, final Options commandLineOptions) throws ParseException
{
Validate.notEmpty(args);
Validate.notNull(commandLineOptions);
this.commandLineOptions = commandLineOptions;
this.command = this.parser.parse(this.commandLineOptions, args);
}
public void printHelp(final String executableName)
{
Validate.notEmpty(executableName);
final HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(executableName, this.commandLineOptions);
}
}
Which have following problems:
As we are throwing exception in constructor:
if args as null. There is no way to printHelp.
if args are invalid. There is no way to printHelp.
I am thinking of following solution:
Solution 1:
public CLIUtil(final String executableName, final Options commandLineOptions) throws ParseException
{
Validate.notNull(commandLineOptions);
this.commandLineOptions = commandLineOptions;
this.executableName = executableName;
}
public void parseArgs(final String[] args) throws ParseException
{
Validate.notEmpty(args);
this.command = this.parser.parse(this.commandLineOptions, args);
}
Problem with this solution is:
User need to call set after constructor. so we are letting client to control the flow.
Solution 2:
public CLIUtil(final String executableName, String[] args, final Options commandLineOptions) throws ParseException
{
if (null == args) {
// show help and throw illegal arg
}
Validate.notNull(commandLineOptions);
this.commandLineOptions = commandLineOptions;
this.executableName = executableName;
try {
this.command = this.parser.parse(this.commandLineOptions, args);
} catch (Parse...) {
// catch exception and show error and printhelp followed by
// throw same exception
}
}
Problem with this solution is:
1) Random placement of validation rule.
Do you have a suggestion which solution is better or suggested improvement?

Categories

Resources