Как я могу unit test закрыть этот входной поток?

У меня есть Runnable по строкам:

    public void run() {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
            //more stuff here
        } 
        catch (Exception e) {
            //simplified for reading
        }
        finally {
            if(inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {}
            }
        }
    }

Как выполнить тест inputStream.close()? В настоящее время я использую Mockito и JUnit. Я знаю, что инъекция inputStream in - это идея, но я не хочу, чтобы ресурсы использовались до тех пор, пока не будет вызван run?(), следовательно, это локальная переменная. Итак, как я могу перепроектировать мой код таким образом, чтобы я мог проверить, был ли вызван контакт?

Ответ 1

Если я правильно понял задачу, это могло бы быть таким образом

static boolean isClosed;

public void run() {
    InputStream inputStream = null;
    try {
        inputStream = new FileInputStream(file) {
            @Override
            public void close() throws IOException {
                isClosed = true;
                super.close();
            }
        };
        // more stuff here

Ответ 2

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

Но я полагаю, что вы не заботитесь о закрытии InputStream. Вы хотите проверить это, потому что вам сказали, что это хорошая практика (и это так). Но я думаю, что вы на самом деле заботитесь о том, что негативное влияние потока остается открытым. Каков эффект?

Попробуйте изменить этот метод, чтобы он не закрывал поток, а затем выполнял его много раз. У вас возникла утечка памяти или закончились файловые дескрипторы или какой-нибудь другой способ? Если это так, у вас есть разумный тест.

В качестве альтернативы, просто зайдите в раскрывающийся InputStream, который может сказать вам, был ли он закрыт или нет. Сделайте пакет защищенным. Это "нечистый", но прагматичный подход.

Ответ 3

Чтобы проверить, вызван ли метод close(), вы можете использовать Mockito.spy() для создания прокси-объекта, который может запоминать вызовы. Шпион делегирует все вызовы базовому InputStream, просто запоминает, что произошло:

InputStream inputStreamSpy = Mockito.spy(inputStream);
// a code that is expected to close your stream goes here ...
Mockito.verify(inputStreamSpy).close();

Это не решит ваши проблемы с инъецированием экземпляра InputStream. Похоже, вам нужен какой-то factory, который может открыть поток для вас, и вы можете издеваться над этим factory в модульных тестах. Позвольте называть эту factory файловую систему:

public class FileSystem {
    public FileInputStream newFileInputStream(File file) {
        return new FileInputStream(file);
    }
}

Теперь вы можете ввести экземпляр FileSystem и не использовать ресурсы до запуска метода выполнения:

public void run() {
    InputStream inputStream = null;
    try {
        inputStream = fileSystem.newFileInputStream(file);
        //more stuff here
    } 
    catch (Exception e) {
        //simplified for reading
    }
    finally {
        if(inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {}
        }
    }
}

@Test
public void runShouldCloseInputStream() {
    InputStream inputStream = ...
    InputStream inputStreamSpy = Mockito.spy(inputStream);
    FileSystem fileSystemMock = Mockito.mock(FileSystem.class);
    when(mockFileSystem.newFileInputStream(Mockito.any(File.class)))
        .thenReturn(inputStreamSpy);

    MyRunnable instance = new MyRunnable(mockFileSystem);
    instance.run();

    verify(inputStreamSpy).close();
}

Шпион может делать больше, чем просто слушать, вы можете научить его изменять поведение с помощью Mockito.when(), точно так же, как вы делали бы с обычным макетом.

Ответ 4

Вы можете сделать вот так:

    try
    {
         inputStream.readLine();        
    }
    catch (IOException e)
    {
        Assert.assertEquals(e.getLocalizedMessage(), "Stream closed");
    }

Ответ 5

Вы можете написать в тесте что-то вроде:

try {
    run();
} catch (IOException e) {
    Assert.fail();
}

Когда ваш метод закроет strem и произойдет исключение, тогда тест завершится с ошибкой.