0

I have the following code:

public class FolderServiceImpl implements FolderService {

    private static final Logger L = LoggerFactory.getLogger(FolderServiceImpl.class);

    public int getStatus(String folderPath) {
        int status = 0;
        File folderStatusFile = new File(folderPath, ".folderstatus");
        if (folderStatusFile.exists()) {
            BufferedReader br = null;
            try {
                br = new BufferedReader(new FileReader(folderStatusFile));
                String line = br.readLine();
                status = Integer.parseInt(line);
            } catch (Exception e) {
                L.error("can't read file " + folderStatusFile.getAbsolutePath(), e);
                status = 4;
            } finally {
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        L.warn("could not close reader ", e);
                    }
                }
            }

        } else {
            status = 3;
        }
        return status;
    }
}

I want to test this method without creating actual files for every case. I should be using Java 1.7, JUnit 4, Mockito and/or PowerMockito.

Any ideas on how to do that?

I am talking about mocking either the data source or simply changeing the input for the method.

My test looks something like this:

`@Rule public TemporaryFolder folder = new TemporaryFolder();

private FolderServiceImpl serviceToTest = new FolderServiceImpl();

private String folderPath;

@Before
public void setUp() {
    folderPath = folder.getRoot().getAbsolutePath();
    try {
        folder.newFile(".folderstatus");
    } catch (IOException e) {
        e.printStackTrace();
    }

}

@Test
public void shouldReturnFolderStatus3WhenFolderStatusIsNotFound() {
    // given
    deleteFolderStatusFile();

    // actual
    int status = serviceToTest.getFolderStatus(folderPath);

    // expected
    assertEquals(3, status);
}

@Test
public void shouldReturnFolderStatus4WhenTheStatusIsUnreadable() {
    // given
    writeStatusToTestFile("Test");

    // actual
    int status = serviceToTest.getFolderStatus(folderPath);

    // expected
    assertEquals(4, status);
}

@Test
public void shouldReturnFolderStatusInTheFile() {
    // given
    writeStatusToTestFile("1");

    // actual
    int status = serviceToTest.getFolderStatus(folderPath);

    // expected
    assertEquals(1, status);

}

private void writeStatusToTestFile(String status) {
    Path file = Paths.get(folder.getRoot().getAbsolutePath(), ".folderstatus");
    try {
        Files.write(file, status.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private void deleteFolderStatusFile() {
    Path file = Paths.get(folder.getRoot().getAbsolutePath(), ".folderstatus");
    try {
        Files.delete(file);
    } catch (IOException e) {
        e.printStackTrace();
    }
}`
radumach
  • 3
  • 4

2 Answers2

0

You have to use something like this:

@RunWith(PowerMockRunner.class)
@PrepareForTest(tests.class)
public class test {

@Test
public void test() throws Exception {
    File fileMock = Mockito.mock(File.class);
    PowerMockito.whenNew(File.class).withArguments(Mockito.anyString(), Mockito.anyString()).thenReturn(fileMock);
    FolderServiceImpl sut = new FolderServiceImpl sut ();
    Mockito.when(fileMock.exists()).thenReturn(true);
    sut.getStatus("");

// Your verifications..
}
}

Powermock will mock the File object which is created in the method getStatus of your class. With Mockito.when you can say what is the return value of folderStatusFile.exists() in your code.

EDIT

I have included the following two jars with maven, but you don't need to use maven: https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4/1.4.6 and https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito/1.4.9 and https://mvnrepository.com/artifact/org.mockito/mockito-all/1.10.19

BenHeid
  • 214
  • 2
  • 14
  • I don't now if I am running it correctly or not but, this approach is getting `java.lang.IncompatibleClassChangeError: Found interface org.powermock.api.mockito.internal.mockcreation.MockCreator, but class was expected`. Also, the idea of the test would be to verify the expected results given different scenarios (no file preset, no int written in file, some int written in file). – radumach Dec 29 '16 at 15:04
  • I edit my post and added the necessary jars. Perhaps you have included the wrong. – BenHeid Dec 29 '16 at 15:49
  • I have those exact dependencies in my classpath and I get the same error – radumach Dec 30 '16 at 07:14
0

Although the answer of @BenHeid may work I'd suggest to change to different approach.

IMHO when ever I use PowerMock(-ito) it is a surrender to bad design. Also the PowerMock solution will confuse test coverage tools since it changes the Applications byte code after it has been instrumented for coverage measurement.


So the approach I'd prefer is to stick to Clean Code and OOP rules.

One of them is separation of concerns.

In your case the method creates some infrastructure classes (dependencies) to work with, namely FileReader and BufferedReader.

But the instantiation of (direct) dependencies is not a responsibility of a class containing business logic.

Therefore I'd suggest to refactor that code out into a separate class:

class ReaderFactory {
    public BufferedReader createFor(File file) throws FileNotFoundException {
        return new BufferedReader(new FileReader(file));
    }
}

Your Class would change to this:

class FolderServiceImpl {
    private static final Logger L = LoggerFactory.getLogger(FolderServiceImpl.class);
    private final ReaderFactory readerFactory;

    FolderServiceImpl(ReaderFactory readerFactory) {
        this.readerFactory = readerFactory;
    }

    public int getStatus(String folderPath) {
        int status = 0;
        File folderStatusFile = new File(folderPath, ".folderstatus");
        // try "with resource" takes care of closing the reader
        try (BufferedReader br = readerFactory.createFor(folderStatusFile);) {
            String line = br.readLine();
            status = Integer.parseInt(line);
        } catch (IOException e) {
            status = 3;
        } catch (Exception e) {
            L.error("can't read file " + folderStatusFile.getAbsolutePath(), e);
            status = 4;
        }
        return status;
    }
}

And your Test would be this:

public class FolderServiceImplTest {

    private static final String ANY_FILE_NAME = "";

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Mock
    private ReaderFactory readerFactory;

    @InjectMocks
    FolderServiceImpl sut;

    @Test
    public void getStatus_FileNotExisting_returnStatus3() throws Exception {
        // arrange
        Mockito.doThrow(new FileNotFoundException("UnitTest")).when(readerFactory).createFor(Mockito.any(File.class));
        // act
        int status = sut.getStatus(ANY_FILE_NAME);
        // assert
        Assert.assertThat("status",status,CoreMatchers.equalTo(3));
    }

    @Test
    public void getStatus_ValidFile_returnFileContentAsInt() throws Exception {
        // arrange
        BufferedReader bufferedReader = Mockito.mock(BufferedReader.class);
        Mockito.doReturn(bufferedReader).when(readerFactory).createFor(Mockito.any(File.class));
        Mockito.doReturn("4711").when(bufferedReader).readLine();
        // act
        int status = sut.getStatus(ANY_FILE_NAME);
        // assert
        Assert.assertThat("status",status,CoreMatchers.equalTo(4711));
    }
}
Timothy Truckle
  • 15,071
  • 2
  • 27
  • 51