Search code examples
javaunit-testingtestingmockitowriter

How to test this file-writing, 3lines function?


This is my method in some service class. It's public so it should be tested. I simply do not know WHAT should I test. I'd mock Writer and spyOn function call, but with this implementation it's impossible (isn't it?)

I'm using Mockito and JUnit

For now, I can only make function to throw and assert that exception

Any help?

@Override
public void initIndexFile(File emptyIndexFile) {
    try {
        Writer writer = new FileWriter(emptyIndexFile);
        writer.write("[]");
        writer.close();
    } catch (IOException e) {
        throw new IndexFileInitializationException(
            "Error initialization index file " + emptyIndexFile.getPath()
        );
    }
}

Solution

  • If you feel that adding the the special content is the business logic and therefore the responsibility of your class, then creating the FileWriter is not (according to the single responsibility pattern.

    So you should use a FileWriterFactory that is injected into your Class under Test. Then you can mock that FileWriterFactory to return a mock implementation of the Writer interface on which in turn you can check that it got the expected String.

    Your CuT would change to this:

    private final WriterFactory writerFactory;
    
    public ClassUnderTest(@Inject WriterFactory writerFactory){
       this.writerFactory = writerFactory;
    }
    
    @Override
    public void initIndexFile(File emptyIndexFile) {
        try {
            Writer writer = writerFactory.create(emptyIndexFile);
            writer.write("[]");
            writer.close();
        } catch (IOException e) {
            throw new IndexFileInitializationException(
                "Error initialization index file " + emptyIndexFile.getPath()
            );
        }
    }
    

    and your test to this:

    class Test{
    
      @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); 
    
      @Mock
      private FileWriterFactory fileWriterFactory;
      private Writer fileWriter = spy(new StringWriter());
      File anyValidFile = new File(".");
      @Test
      public void initIndexFile_validFile_addsEmptyraces(){
         //arrange
         doReturn(fileWriter).when(fileWriterFactory).create(any(File.class));
    
         // act
         new ClassUnderTest(fileWriterFactory).initIndexFile(anyValidFile);
    
         //assert
         verify(fileWriterFactory)create(anyValidFile);
         assertEquals("text written to File", "[]", fileWriter.toString());
         verify(fileWriter).close();
      }
    }
    

    in addition you could easily check that your CuT intercepts the IOException:

      @Rule
      public ExpectedException exception = ExpectedException.none();
    
      @Test
      public void initIndexFile_missingFile_IndexFileInitializationException(){
         //arrange
         doReturnThrow(new IOException("UnitTest")).when(fileWriterFactory).create(any(File.class));
    
         //assert
         exception.expect(IndexFileInitializationException.class);
         exception.expectMessage("Error initialization index file "+anyValidFile.getPath());
    
         // act
         new ClassUnderTest(fileWriterFactory).initIndexFile(anyValidFile);
      }
    

    Nice! a factory just to test 3 lines of code! – Nicolas Filotto

    This is a good point.

    The question is: will there be any method within that class ever interacting with the File object directly and needs to create the FileWriter afterwards?

    If the answer is "no" (as it is most likely) following the KISS principle you should inject a Writer object directly instead of the factory and have your methods without the File parameter.

    private final Writer writer;
    
    public ClassUnderTest(@Inject Writer writer){
       this.writer = writer;
    }
    
    @Override
    public void initIndexFile() {
        try {
            writer.write("[]");
            writer.close();
        } catch (IOException e) {
            throw new IndexFileInitializationException(
                "Error initialization index file " + emptyIndexFile.getPath()
            );
        }
    }
    

    modified test:

    class Test{       
      @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); 
      @Rule public ExpectedException exception = ExpectedException.none();
    
      @Mock
      private FileWriterFactory fileWriterFactory;
      @Mock
      private Writer failingFileWriter;
      private Writer validFileWriter = spy(new StringWriter());
      File anyValidFile = new File(".");
      @Test
      public void initIndexFile_validFile_addsEmptyraces(){
         //arrange         
         // act
         new ClassUnderTest(validFileWriter).initIndexFile();
    
         //assert
         verify(fileWriterFactory)create(anyValidFile);
         assertEquals("text written to File", "[]", fileWriter.toString());
         verify(fileWriter).close();
      }
    
      @Test
      public void initIndexFile_missingFile_IndexFileInitializationException(){
         //arrange
         doReturnThrow(new IOException("UnitTest")).when(failingFileWriter).write(anyString());
    
         //assert
         exception.expect(IndexFileInitializationException.class);
         exception.expectMessage("Error initialization index file "+anyValidFile.getPath());
    
         // act
         new ClassUnderTest(fileWriterFactory).initIndexFile(anyValidFile);
      }
    }