Writing Junit test to cover exception and catch block

14,810

Solution 1

In such situations you may often consider the introduction of additional dependencies to your class. Here is what I mean with a rough example. Create a factory for readers:

interface BufferedReaderFactory
{
    public BufferedReader createBufferedReader(String fileName) throws IOException;
}

Then you will have a trivial implementation that hardly needs any testing, e.g. something similar:

class BufferedReaderFactoryImpl implements BufferedReaderFactory
{
    @Override
    public BufferedReader createBufferedReader(String fileName) throws IOException
    {
        return new BufferedReader(new FileReader(fileName));
    }
}

Then you have to find a way to inject this dependency into your class. I usually use Guice in my daily work but you may try something as simple as using constructor injection and making your method non static. Here is an example:

class CsvFileReader
{
    private final BufferedReaderFactory factory;

    public CsvFileReader(BufferedReaderFactory factory)
    {
        this.factory = factory;
    }

    public List<Student> readCsvFile(String fileName)
    {

        BufferedReader fileReader = null;
        try
        {
            fileReader = factory.createBufferedReader(fileName);
            ...
        }
        catch(IOException e)
        {
            ...
        }
        finally
        {
            ...
        }
        return new LinkedList<>();
    }
}

With a mocking framework like Mockito the behavior of this class in case of IOException-s is easier to test now (note that you may also return mocks that throw exceptions from the factory). Here is a sample:

@RunWith(MockitoJUnitRunner.class)
public class MyTest
{
    @Mock
    private BufferedReaderFactory mockFactroy;

    @Test
    public void testIOException() throws IOException
    {
        String ivalidFileName = "invalid.txt";
        //throw exception in case that invalid file name is passed to the factory
        Mockito.when(mockFactroy.createBufferedReader(ivalidFileName)).thenThrow(new IOException("Hello!"));

        CsvFileReader csvFileReader = new CsvFileReader(mockFactroy);
        //invoke with a factory that throws exceptions
        csvFileReader.readCsvFile(ivalidFileName);
        //...
        //and make a sensible test here, e.g. check that empty list is returned, or proper message is logged, etc.
    }
}

You may do that without Mockito, of course - by implementing a test factory. But this is more cumbersome especially in more complicated use cases. Once the IOException is thrown you will get appropriate coverage report by JaCoCo.

Also mind a limitation of JaCoCo mentioned here, in section Source code lines with exceptions show no coverage. Why?

Solution 2

Given the current signature of your method under test, getting to full coverage isn't easy: your catch block is only executed when an exception is thrown within your try block.

One way to solve this: do not pass in the file name, but the reader object itself. Like:

public static List<Student> readCsvFile(String fileName) {
  return readCsvFile(new BufferedReader(fileName));
}

static List<Student> readCsvFile(BufferedReader reader) { 
  try {
    ...
  } catch( ...

Now you can write several specific unit tests for that second method. You keep your tests that simply do "correct" reading; but you add one where you pass in a mocked reader object ... that simply throws an Exception at some point. Please note that I made that new method just package protected - you probably don't want to use that "public"; and making it private would prevent it from being unit tested.

That should help you achieving full coverage. Of course you will also need at least one test to "cover" the string-taking method, too.

Some notes:

  1. Be careful about re-inventing the wheel. There are many existing CSV parsers already. And be assured: writing a correct CSV parser that is able to deal with all "correct" input CSV is much harder than it sounds. If this is not for "learning purposes" I strongly advise to not write your own CSV parser.
  2. Be careful about making such things static. As said, a real CSV parser is a complicated thing, and worth its complete own class. So no static helper methods - a normal class which you instantiate to then call methods on it (that would also for using dependency injection which would help with the problem you are asking about ... getting exceptions thrown within try blocks)
  3. You are catching Exception in your code example. Don't do that - try to catch exactly those exceptions that your code can actually produce (probably IOException in your case).
Share:
14,810
ronypatil
Author by

ronypatil

Updated on June 04, 2022

Comments

  • ronypatil
    ronypatil almost 2 years

    I have written Junit test case for following function. When checked JACOCO test coverage. It is showing only try block is covered by test case. I am newbie to writing test cases. How the exceptions and catch block can be covered in test cases

    Here is a method

      public static List<Student> readCsvFile(String fileName)
        {
    
           BufferedReader fileReader = null;
    
    //logic to read file    
    }
    catch (Exception e)
    {
       System.out.println("Error in CsvFileReader !!!");
       e.printStackTrace();
            } finally
            {
                try
                {
                    fileReader.close();
                } catch (IOException e)
                {
                    System.out.println("Error while closing fileReader !!!");
                    e.printStackTrace();
                }
            }
            return students;
        }
    

    And TestMethod

    @Test
        public void ReadCsvFileTest()
        {
            String fileName = "test.csv";
            List<Student> result = new ArrayList<Student>();
            result = CsvFileReader.readCsvFile(fileName);
    
            Student student1 = null;
    
            Iterator<Student> it = result.iterator();
            while (it.hasNext())
            {
                Student s = it.next();
                if ("471908US".equals(s.getId()))
                {
                    student1 = s;
                    break;
                }
            }
    
            assertTrue(student1 != null);
        }