Home > other >  Test for thrown exception in constructor method not behaving as expected
Test for thrown exception in constructor method not behaving as expected

Time:01-16

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

CodePudding user response:

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

CodePudding user response:

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.

  •  Tags:  
  • java
  • Related