Тест метода, который читает данные из файла (надо использовать mock)

Прохожу обучение по Java. У меня в проекте есть функция readData(filePath), которая читает текст из файла и возвращает лист строк. Для её тестирования создаю в тесте файл с данными, на нём собственно и тестирую функцию. Преподаватель сказал, что надо использовать моки. Я погуглил, посмотрел примеры использования, но так и не понял, как применить к моему тесту. Подскажите, пожалуйста, как это сделать. Ниже мой тест и метод.

import org.testng.annotations.Test;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class CubeDataReaderTest {

    File testFile;
    CubeDataReader cubeDataReader;

    @BeforeClass
    public void setUp() {
        testFile = new File("testData.txt");
        cubeDataReader = new CubeDataReader();

        try (FileWriter fileWriter = new FileWriter(testFile)) {
            fileWriter.write("1.0 2.0 3.0 4.0");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @AfterClass
    public void tierDown() {
        testFile.delete();
    }

    @Test
    public void readDataPositiveTest() throws CustomException {
        List<String> expected = new ArrayList<>();
        expected.add("1.0 2.0 3.0 4.0");
        List<String> actual = cubeDataReader.readData(testFile.getPath());

        Assert.assertEquals(expected, actual);
    }

    @Test(expectedExceptions = CustomException.class)
    public void readDataNegativeTest() throws CustomException {
        cubeDataReader.readData("notExistingFile.txt");
    }
}
public class CubeDataReader {
    static Logger logger = LogManager.getLogger();

    public List<String> readData(String filePath) throws CustomException {
        List<String> lines;
        try (FileReader fileReader = new FileReader(filePath);
             BufferedReader bufferedReader = new BufferedReader(fileReader)) {
            lines = bufferedReader.lines().collect(Collectors.toList());
            logger.info("File was read");

        } catch (FileNotFoundException e) {
            logger.error("File not found.", e);
            throw new CustomException("File not found.", e);

        } catch (IOException e) {
            logger.error("Cannot read file.", e);
            throw new CustomException("Cannot read file.", e);
        }
        return lines;
    }
}

Без кода cubeDataReader сложно чем-то помочь

добавил метод

Преподаватель неправ.

  1. В таком тесте как раз лучше создать файл, как вы и сделали. Можно спорить, что такой тест скорее должен называться не “юнит-тест”, а “интеграционный тест”, но это неважно. Важно, что он быстрый и стабильный. А моки реально полезны для медленных, нестабильных и неподконтрольных зависимостей.
  2. Даже если очень захотеть мок, в этом коде его не получится использовать. Для этого нужно, чтобы объект FileReader не создавался внутри метода readData, а передавался снаружи как параметр (как правило, в конструктор CubeDataReader). Вот тогда его можно замокать, и в тесте передать в конструктор мок. Но с FileReader всё это бесмыссленно. Просто не нужно.
  3. Точнее, в последних версиях Mockito появилась возможность замокать конструктор (почти) любого класса. Так что технически возможно замокать конструктор FileReader. Но незачем.

P.S. Нафига вообще нужен класс CubeDataReader, если он делает почти то же самое, что и BufferedReader? Сомнительная польза.

1 лайк

Если в текущей реализации действительно невозможно замокать инстанс под капотом - грош цена “этой вашей джаве”. Я без претензий, просто удивлен не на шутку.

По-поводу надо/не надо моки - и согласен и нет: если кейс с FileNotFoundException еще можно покрыть не создав файл, то чтобы вывалить IOException уже недостаточно “создать файл с контентом”, надо еще права выставить (и не факт, что отсутствие r флага рейзнет его, кстати), а если надо, условно, покрыть еще один эджкейс - варианты нещадно иссякают. Я к тому, что моки нужны не только для клиент-серверного IO

З.Ы. в джаве не шарю, и после вашего коммента, почему-то даже рад

уберите создание инстанса
FileReader из класса CubeDataReader и передавайте как аргумент конструктора

в тесте вместо настоящего FileReader передавайте в конструктор мок

в моке проверьте что его звали из readData
кстати исключения из мока кидать можно как захочется - для этого он и существует

Воу, палехче!

Во-первых, я как раз сказал, что в джаве МОЖНО замокать инстанс под капотом (“в последних версиях Mockito”). Во-вторых, да, вывалить IOException действительно сложнее, согласен. Но главное, что всё это не нужно. Незачем вообще ловить IOException и оборачивать. И незачем это тестировать. И это никак от Java не зависит.

Действительно, если цель урока потрогать моки - то пример для этого - некорректный, либо очень узкий в данном направлении.

@asolntsev, @BabyRoot
Может проще/действеннее привести пример “как реализовать хотелку”, чем обсуждать “зачем это делать”, вне зависимости от того, насколько тестируемый код высосан из пальца?
Вопрос то задан по существу

2 лайка

Если Вы пишете юнит тесты то требование верно. Вы должны тестировать только публичный метод readData но не его зависимости, поэтому нужно замокать классы FileReader и BufferedReader (либо поставить заглушку) смотрите на тот функционал который предлагает Ваш фреймворк. Никаких чтений файлов и т.п. это не интеграционное тестирование поэтому здесь все должно быть изолировано. Можете представить что Вы пишете тесты до того как этот код написан, реализации внешних классов которые будут использованы еще нет, а есть только интерфейсы и все. И не нужно копипастить чужой код попробуйте разобраться сами, здесь очень простой случай для его решения достаточно прочитать только мануал как мокать классы внутри публичного метода. И не надо ничего менять в реализации метода который Вы тестируете (он создан ровно таким как надо для того чтобы Вы научились использовать моки именно в таких случаях)