如何测试外部class执行的IO?

How to test IO performed by external class?

我有一个简单的 class 可以将数据写入 CSV 文件:

public class CsvFileWriter {
    private static Logger logger = LogManager.getLogger();

    public static void writeDataToCsvFile(String filePath, List<String[]> data) {
        File file = new File(filePath);
        try (FileWriter outputFile = new FileWriter(file)) {
            CSVWriter writer = new CSVWriter(outputFile, ' ',
                    CSVWriter.NO_QUOTE_CHARACTER,
                    CSVWriter.DEFAULT_ESCAPE_CHARACTER,
                    CSVWriter.DEFAULT_LINE_END);
            writer.writeAll(data);
        } catch (IOException e) {
            logger.error("Unable to find the specified path", e);
            throw new ProjectException(e);
        }
    }
}

我想知道如何测试这个class中包含的方法。我发现可以使用 Mockito 完成此操作的信息。是这样吗?我该怎么做?

您不需要 Mockito 或任何模拟来测试它(故障处理和日志记录除外)。 您应该使用 Junit 5 中的 @TempDir,或 Junit 4 中的 TemporaryFolder

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

class CsvFileWriterTest {
    @Test
    void createsCsvFile(@TempDir final Path temp) {
        final var csv = temp.resolve("test.csv");
        CsvFileWriter.writeDataToCsvFile(
                csv.toString(),
                List.of(
                        new String[]{"1", "abc"},
                        new String[]{"2", "def"}
                )
        );
        Assertions.assertTrue(csv.toFile().exists());
    }

    @Test
    void writesDataToCsv(@TempDir final Path temp) throws IOException {
        final var csv = temp.resolve("test.csv");
        CsvFileWriter.writeDataToCsvFile(
                csv.toString(),
                List.of(
                        new String[]{"1", "abc"},
                        new String[]{"2", "def"}
                )
        );
        Assertions.assertEquals(
                Files.readAllLines(csv),
                List.of("1 abc", "2 def")
        );
    }
}

虽然您可能想要重构您的 class 以便更易于测试:

@lombok.RequiredArgsConstructor
public class CsvFileWriter {
    private static final Logger LOG = LogManager.getLogger();
    private final Path file;
    private final Function<Writer, CSVWriter> csvfactory;

    public CsvFileWriter(final Path file) {
        this(
            file,
            writer ->
                new CSVWriter(writer, ' ',
                    CSVWriter.NO_QUOTE_CHARACTER,
                    CSVWriter.DEFAULT_ESCAPE_CHARACTER,
                    CSVWriter.DEFAULT_LINE_END
                )
        );
    }

    public void write(final List<String[]> data) {
        try (var csv = this.csvfactory.apply(Files.newBufferedWriter(this.file))) {
            csv.writeAll(data);
        } catch (IOException ex) {
            LOG.error("Unable to find the specified path", ex);
            throw new UncheckedIOException(ex);
        }
    }
}

现在您可以将工厂方法传递给构造函数,并使用它来模拟 CSVWriter。例如,您可以测试您是否正确处理了 IOException class:

@Test
void handlesIoException(@TempDir final Path temp) {
    final var mock = Mockito.mock(CSVWriter.class);
    Mockito.doThrow(IOException.class).when(mock).writeAll(Mockito.anyList());
    final var writer = new CsvFileWriter(temp.resolve("not.csv"), wrt -> mock);
    Assertions.assertThrows(
        UncheckedIOException.class,
        () -> writer.write(List.of())
    );
}