I am trying to write some unit tests to see if a logging method gets called for csv exceptions. The flow goes something like this:
CsvToBean is used to parse some info and each bean that is produced has some work done on it.
After all this, CsvToBean.getCapturedExceptions().forEach() is used to processed the exceptions.
How to I create some of these exceptions for testing?
public void parseAndSaveReportToDB(Reader reader, String reportFileName,ItemizedActivityRepository iaRepo,
ICFailedRecordsRepository icFailedRepo,
String reportCols) throws Exception {
try {
CsvToBean<ItemizedActivity> csvToBean = new CsvToBeanBuilder<ItemizedActivity>(reader).withType(ItemizedActivity.class).withThrowExceptions(false).build();
csvToBean.parse().forEach(itmzActvty -> {
itmzActvty.setReportFileName(reportFileName);
String liteDesc = itmzActvty.getBalanceTransactionDescription();
if (liteDesc.contains(":")) {
liteDesc = liteDesc.substring(liteDesc.indexOf(":")+1).trim();
}
itmzActvty.setLiteDescription(liteDesc);
itmzActvty.setAmount(convertCentToDollar(itmzActvty.getAmount()));
iaRepo.save(itmzActvty);
});
log.info("Successfully saved report data in DB");
csvToBean.getCapturedExceptions().forEach(csvExceptionObj -> logFailedRecords(reportFileName, csvExceptionObj, icFailedRepo, reportCols));
reader.close();
} catch (Exception ex) {
log.error("Exception when saving report data to DB", ex);
throw ex;
}
}
In this code I need to trigger the logFailedRecords method. To do so I need to fill the captured exceptions queue with an exception. I don't know how to get an exception in there.
What I have is not much since I keep hitting walls
#Test
public void testParseAndSaveReportToDBWithExceptions() throws Exception {
// CsvException csvExceptionObject = new CsvException("testException");
CsvToBean<ItemizedActivity> csvToBean = mock(CsvToBean.class);//<ItemizedActivity>(reader).withType(ItemizedActivity.class).withThrowExceptions(false).build().class);
BufferedReader reader = mock(BufferedReader.class);
ReportingMetadata rmd = this.getReportingMetadata();
verify(this.reportsUtil).parseAndSaveReportToDB(reader,"test.csv",
this.iaRepo,this.icFailedRepo,rmd.getReportCols());
// System.out.println(csvToBean.getCapturedExceptions().toString());
}
Related
I have the following code that reads data from a csv file that I am trying to write a unit test for. I am unsure of how to go about it.
public class BudgetTags implements BudgetTagsList{
// State variables
private Set<String> tags = new TreeSet<>();
private String tag_file_path;
public BudgetTags(String tag_file_path){
//Retrieve tags from tag file
this.tag_file_path = tag_file_path;
this.retrieveTags();
}
public void retrieveTags() {
String line = "";
try{
// Begin reading each line
BufferedReader br = new BufferedReader(new FileReader(this.tag_file_path ));
while((line = br.readLine()) != null){
String[] row = line.split(",");
this.tags.add(row[0]); //Assume correct file format
}
br.close();
} catch (IOException e){
System.out.println("Fatal exception: "+ e.getMessage());
}
}
}
Note that the method retrieveTags(); is not allowing me to specify an additional FileNotFoundException since it extends IOException. It is being tested in the following manner:
#Test
#DisplayName("File name doesn't exist")
void testRetrieveTag3() {
String path = "test\\no_file.csv";
//Instantiate new tags object
BudgetTags tags = new BudgetTags(path);
IOException thrown = assertThrows(IOException.class, () -> tags.retrieveTags());
}
The variable path does not exist so I am expecting the test to catch the IOException, (although I would prefer a FileNotFoundException) . When I run this particular test, I receive an AssertionFailedError How can I restructure my test so that it catches the FileNotFoundException when a new tags object is instantiated, since retrieveTags() is called when a new tags object is generated?
The method retrieveTags() will not allow me to specify
The method is not actually throwing the exception but catching it. What you actually need to test is that your catch block gets executed. If all you want to do on catching the exception is printing the error, test system.out can help you assert the print statement
Your assertThrows test is failing becuase it's impossible for the constructor to throw an IOException. For one, it's a checked exception, which means both the constructor and the method would require a throws IOException clause. Second, you catch the exception; it's not thrown out of the method.
Based on your test, it should look more like this:
public class BudgetTags implements BudgetTagsList {
private final Set<String> tags = new TreeSet<>();
private String tagFilePath;
public BudgetTags(String tagFilePath) throws IOException {
this.tagFilePath = tagFilePath;
retrieveTags(); // can throw IOException
}
public void retrieveTags() throws IOException {
// note: use try-with-resources to handle closing the reader
try (BufferedReader br = new BufferedReader(new FileReader(tagFilePath))) {
String line;
while ((line = br.readLine()) != null) {
String row = line.split(",");
tags.add(row[0]);
}
}
// don't catch the exception; your test indicates you want it
// thrown out to the caller
}
}
class BudgetTagsTests {
#Test
#DisplayName("File does not exist")
void testRetrieveTags3() {
String tagFilePath = "test/no_file.csv";
// note: we need to test on the constructor call, because you call
// 'retrieveTags()' in it.
assertThrows(FileNotFoundException.class, () -> new BudgetTags(tagFilePath));
}
}
By passing FileNotFoundException.class, the test will fail if any other IOException is thrown.
You should not be catching the IOException the way you are, anyway. Yes, you log it, which means if you look at the logs you'll be aware that something went wrong. But other code won't know something went wrong. To that code, it will appear as if there were simply no tags in the file. By throwing the IOException out to the caller of retrieveTags(), you're letting the caller react to the exception as needed. And if the call succeeds, but the tags are empty, then it knows the file exists but simply had no tags.
Also, you say:
Note that the method retrieveTags(); is not allowing me to specify an additional FileNotFoundException since it extends IOException.
I'm not sure what exactly you tried from that statement, but it is possible to catch more specific exceptions even though you're also catching the more general exception. It's just that the order of the catch blocks matter:
try {
somethingThatThrowsIOException();
} catch (FileNotFoundException ex) {
// do something special for when the file doesn't exist
} catch (IOException ex) {
// handle general exception
}
The more specific exception must be caught before the more general exception.
I have two processes - FileWriter and FileReader.
FileWriter has a write() method which creates the file if it's missing and writes to the file. FileReader has a read() method which reads from the file and throws an CustomException if the file is not available.
During the normal application run, FileWriter.write() method is executed first followed by FileReader.read() method. Since the file will be always present, read() method never throws my CustomException unless something went wrong with FileWriter.
I am writing junit testcases for both these classes. When testing both classes independent of each other, I found my custom exception is being thrown when file is not present. For a really convoluted reason, I want the testcase to be marked as success and execute the next test. To achieve this, I did the below:
#Test
public void testRead() throws CustomException {
boolean assumeTestcasePassed = false;
FileReader fileReader = new FileReader();
String fileContent = null;
try {
fileContent = fileReader.read();
} catch (CustomException e) {
assumeTestcasePassed = true;
}
if(assumeTestcasePassed){
assertTrue(true);
} else {
assertTrue("File is empty", fileContent != null);
}
}
Is there a better way to achieve what I am doing here?
If I understand correctly, you are expecting an exception to be thrown, just do this:
#Test
public void testRead() {
try {
assertTrue(new FileReader().read() != null);
} catch (CustomException e) {
// Test passes
}
}
You can also annotate like this answer: How do you assert that a certain exception is thrown in JUnit 4 tests?
You can expect the tested code to throw an exception in the #Test annotation:
#Test(expected = CustomException.class)
public void testRead() throws CustomException {
FileReader fileReader = new FileReader();
fileContent = fileReader.read();
}
My java app calls a rest endpoint and in the response body is a 10GB XML file. Before I send the rest quest, I ask the service how many records will be in the file. I then retrieve the file. When I run my app, the file is saved successfully but only roughly 50% of the expected records. There are 2 reasons the file doesn't have all the records:
The file sent from the rest endpoint only has 50% of the expected records
My app is falling over when before it has finished downloading
My question is, if in scenario 2 and my app falls over, would I see an exception stating so? I do not see an exception, in fact, I see my log statement after the save is saved saying 'File successfully saved'.
EDIT: I have downloaded the file outside of my app, via a curl request and the same thing happened - only 50% of the expected population was downloaded. This proves the issue isn't with my file-saving logic.
public void saveFile() {
try {
downloadAndSaveFile();
} catch (Exception e) {
LOGGER.error("A error has occurred processing all content, caused by {}", e.getMessage(), e);
throw new RuntimeException(e);
}
}
private void downloadAndSaveFile() throws Exception {
long recordCount = countRecords();
LOGGER.info("Number of records to process is {}", recordCount);
if (recordCount > 0 ) {
InputStream dataToSave = getAllContent();
saveStream(dataToSave);
LOGGER.info("File successfully saved.");
} else {
LOGGER.error("No content to retrieve");
throw new RuntimeException("There are no records to process");
}
}
public InputStream getAllContent() throws Exception {
return callRestEndpoint(webTarget).readEntity(InputStream.class);
}
private Response callRestEndpoint(WebTarget target) throws InterruptedException {
Response response = null;
for (int numberOfTries = 0; numberOfTries < reconnectRetries; numberOfTries++) {
try {
response = makeGetRequest(target);
if (OK.getStatusCode() == response.getStatus()) {
break;
}
} catch (Exception ex) {
retryRequest(numberOfTries, ex);
}
}
return response;
}
public void saveStream(InputStream inputStream) throws IOException {
File fileToCreate = new File(fileName);
if (!fileToCreate.exists()) {
fileToCreate.mkdirs();
}
Files.copy(
inputStream,
fileToCreate.toPath(),
StandardCopyOption.REPLACE_EXISTING
);
closeQuietly(inputStream);
}
Is my file saving logic correct?
No.
if (!fileToCreate.exists()) {
fileToCreate.mkdirs();
}
Here you are creating every element in fileToCreate as a directory, including the final element. So trying to open it later as a file will fail. And the exists() test is pointless. It should be:
fileToCreate.getParentFile().mkdirs();
if in scenario 2 and my app falls over, would I see an exception stating so
Yes, provided you print or log it somewhere. The method will definitely throw one.
I have this code where I'm catching some exception and throwing a custom exception instead.
#Override
public void config() throws CustomException{
File jsonFile = new File("config.json");
try {
ConfigMapper config = mapper.readValue(jsonFile, ConfigMapper.class);
try {
this.instanceId = Integer.parseInt(config.getConfig().getClientId());
this.configParams = config.getConfig().getConfigParams();
} catch (NumberFormatException ex) {
throw new CustomException("Please provide a valid integer for instance ID", ex);
//LOGGER.log(Level.SEVERE, "error initializing instanceId. Should be an integer " + e);
}
} catch (IOException ex) {
throw new CustomException("Error trying to read/write", ex);
// LOGGER.log(Level.SEVERE, "IOException while processing the received init config params", e);
}
}
I need to write a unit test for this and below is how I wrote it.
#Test
public void should_throw_exception_when_invalid_integer_is_given_for_instanceID(){
boolean isExceptionThrown = false;
try{
Mockito.doThrow(new NumberFormatException()).when(objectMock).config();
barcodeScannerServiceMock.config();
} catch (CustomException ex) {
isExceptionThrown = true;
}
assertTrue(isExceptionThrown);
}
But its throwing a number format exception and not the CustomException as I want it to be. But this makes sense as I'm using the mock object to throw the exception as a result of which my code logic is not executed. But if that's the case, how do I test this scenario? Please advice.
1.) Remove the line Mockito.doThrow(new NumberFormatException()).when(objectMock).config();
2.) Change the Client-ID in your JSON-File to something that cannot be converted to an Integer.
this.instanceId = Integer.parseInt(config.getConfig().getClientId()); will fail due to that and thus throw an exception.
One advice regarding names: The name of your test method should be what's written in the Java-Doc. Just name it "testCustomException" & explain the methods function in the Java-Documentation. There are Naming-Conventions in Java (click here) which are basically general guidelines.
Practicing these is very helpful as it allows you to quickly get into your code again after not working on it for a month or so due to the increased readability.
I have the following class:
public class FileLoader {
private Map<Brand, String> termsOfUseText = new HashMap<Brand, String>();
public void load() {
for (Brand brand : Brand.values()) {
readAndStoreTermsOfUseForBrand(brand);
}
}
private void readAndStoreTermsOfUseForBrand(Brand brand) {
String resourceName = "termsOfUse/" + brand.name().toLowerCase() + ".txt";
InputStream in = this.getClass().getClassLoader().getResourceAsStream(resourceName);
try {
String content = IOUtils.toString(in);
termsOfUseText.put(brand, content);
} catch (IOException e) {
throw new IllegalStateException(String.format("Failed to find terms of use source file %s", resourceName),e);
}
}
public String getTextForBrand(Brand brand) {
return termsOfUseText.get(brand);
}
}
Brand is an enum, and I need all the valid .txt files to be on the classpath. How do I make the IOException occur, given that the Brand enum contains all the valid brands and therfore all the .txt files for them exist?
Suggestions around refactoring the current code are welcome if it makes it more testable!
Three options I see right off:
Use PowerMock to mock IOUtils.toString(). I consider PowerMock to be quite a last resort. I'd rather refactor the source to something a little more test-friendly.
Extract the IOUtils call to a protected method. Create a test-specific subclass of your class that overrides this method and throws the IOException.
Extract the InputStream creation to a protected method. Create a test-specific subclass to override the method and return a mock InputStream.
I would suggest a bit of refactoring. All your methods are void, this usually means they are not functional.
For example, you can extract this functionality:
private String readTermsOfUseForBrand(InputStream termsOfUserIs) {
try {
String content = IOUtils.toString(in);
return content;
} catch (IOException e) {
throw new IllegalStateException(String.format("Failed to find terms of use source file %s", resourceName), e);
}
return null;
}
So that we can assert on the String result in our tests.
Of course this is not functional code, as it reads from an Input Stream. And it does so with IOUtils.toString() method that cannot be mocked easily (well, there's PowerMock but as Ryan Stewart said it's the last resort).
To test IO exceptions you can create a failing input stream (tested with JDK7):
public class FailingInputStream extends InputStream {
#Override
public int read() throws IOException {
throw new IOException("Test generated exception");
}
}
And test like that:
#Test
public void testReadTermsOfUseForBrand() {
FileLoader instance = new FileLoader();
String result = instance.readTermsOfUseForBrand(new FailingInputStream());
assertNull(result);
}
Missing file will cause NullPointerException because getResourceAsStream will return null and you will have in==null. IOException in this case may actually be pretty rare. If it's critical for you to see it, I can only think of instrumenting this code to throw it if code is executed in test scope. But is it really that important?
I would use a mock to accomplish this.
Example (untested, just to give you some thought):
#Test(expected=IllegalStateException.class)
public void testThrowIOException() {
PowerMockito.mockStatic(IOUtils.class);
PowerMockito.when(IOUtils.toString()).thenThrow(
new IOException("fake IOException"));
FileLoader fileLoader = new FileLoader();
Whitebox.invokeMethod(fileLoader,
"readAndStoreTermsOfUseForBrand", new Brand(...));
// If IllegalStateException is not thrown then this test case fails (see "expected" above)
}
Code below is completely untested
To cause the IOException use:
FileInputStream in = this.getClass().getClassLoader().getResourceAsStream(resourceName);
in.mark(0);
//read some data
in.reset(); //IOException
To test the IOException case use:
void test
{
boolean success = false;
try
{
//code to force ioException
}
catch(IOException ioex)
{
success = true;
}
assertTrue(success);
}
In JUnit4
#Test(expected=IOException.class)
void test
{
//code to force ioException
}
Other JUnit
void test
{
try
{
//code to force IOException
fail("If this gets hit IO did not occur, fail test");
}
catch(IOException ioex)
{
//success!
}
}