Вопрос или проблема
Ниже приведённый код предназначен для записи содержимого в файл.
public String saveSecretToFile() {
File file = new File("test.txt");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(secretValue);//secretValue — это содержимое
} catch (IOException e) {
LOGGER.error("Ошибка записи в файл: {}", e.getMessage());
}
}
Блок catch не охвачен в тестовом случае JUnit.
@InjectMocks
private MyHelper myHelper;
try (MockedStatic<FileOutputStream> mockedStatic = Mockito.mockStatic(FileOutputStream.class)) {
FileWriter mockFile = mock(FileWriter.class);
OutputStream mockFileWriter = Mockito.spy(new FileOutputStream(String.valueOf(mockFile)));
doThrow(new IOException("Ошибка ввода-вывода")).when(mockFileWriter).write(anyString().getBytes());
IOException thrown = assertThrows(IOException.class, () -> myHelper.saveToFile("secretName"));
}
Я прочитал, что замена BufferedWriter на мок не сработает, поскольку внутренний OutputStream FileWriter должен быть замокирован. Ожидаемый результат теста — выброс исключения. Но результат показывает java.lang.AssertionError: ожидалось, что будет выброшено java.io.IOException, но ничего не было выброшено
Я бы сделал что-то подобное:
1- Сначала реализуйте запись в мок.
public class MyHelper {
private final BufferedWriter writer;
// Конструктор для внедрения зависимостей
public MyHelper(BufferedWriter writer) {
this.writer = writer;
}
public void saveSecretToFile(String secretValue) {
try {
writer.write(secretValue);
writer.flush();
} catch (IOException e) {
LOGGER.error("Ошибка записи в файл: {}", e.getMessage());
throw new RuntimeException("Ошибка записи в файл", e); // Исключение для обозначения ошибки
}
}
}
2- Напишите оба теста: для успешного сценария и для сценария с IOException, используя мок моего предыдущего сервиса, и проверьте, что метод был вызван.
Есть проверка, которая подтверждает, что метод был вызван.
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import java.io.BufferedWriter;
import java.io.IOException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.*;
class MyHelperTest {
@Mock
private BufferedWriter mockWriter;
@InjectMocks
private MyHelper myHelper;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testSaveSecretToFile_Success() throws IOException {
// Подготовка
String secretValue = "secret";
// Действие
myHelper.saveSecretToFile(secretValue);
// Проверка
verify(mockWriter, times(1)).write(secretValue);
verify(mockWriter, times(1)).flush();
}
@Test
void testSaveSecretToFile_ThrowsException() throws IOException {
// Подготовка
String secretValue = "secret";
doThrow(new IOException("Ошибка ввода-вывода")).when(mockWriter).write(anyString());
// Действие и проверка
Exception exception = assertThrows(RuntimeException.class, () -> myHelper.saveSecretToFile(secretValue));
assertTrue(exception.getCause() instanceof IOException);
assertEquals("Ошибка записи в файл", exception.getMessage());
verify(mockWriter, times(1)).write(secretValue); // Проверка, что запись была вызвана перед исключением
}
}
Ответ или решение
Чтобы протестировать метод saveSecretToFile
, который использует BufferedWriter
для записи данных в файл, мы можем использовать библиотеку JUnit и Mockito. В вашем коде основной проблемой является то, что метод записи не защищен от обработки исключений ввода-вывода, поэтому необходимо немного изменить метод и тест сделать корректным.
1. Изменение метода saveSecretToFile
Чтобы улучшить обработку исключений и упростить тестирование, сделаем следующий шаг:
- Добавим возможность передачи
BufferedWriter
в конструктор, чтобы мы могли легко его замокировать в тестах. - Будем выбрасывать
RuntimeException
, если произошла ошибка, что даст возможность тесту поймать это исключение.
import java.io.BufferedWriter;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyHelper {
private final BufferedWriter writer;
private static final Logger LOGGER = LoggerFactory.getLogger(MyHelper.class);
// Конструктор для внедрения зависимости
public MyHelper(BufferedWriter writer) {
this.writer = writer;
}
public void saveSecretToFile(String secretValue) {
try {
writer.write(secretValue);
writer.flush();
} catch (IOException e) {
LOGGER.error("Error writing to file: {}", e.getMessage());
throw new RuntimeException("Error writing to file", e); // Исключение для указания на ошибку
}
}
}
2. Написание юнит-тестов
Теперь мы можем написать тесты для обработки как успешного, так и неудачного сценариев:
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import java.io.BufferedWriter;
import java.io.IOException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.*;
class MyHelperTest {
@Mock
private BufferedWriter mockWriter;
@InjectMocks
private MyHelper myHelper;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testSaveSecretToFile_Success() throws IOException {
// Arrange
String secretValue = "secret";
// Act
myHelper.saveSecretToFile(secretValue);
// Assert
verify(mockWriter, times(1)).write(secretValue);
verify(mockWriter, times(1)).flush();
}
@Test
void testSaveSecretToFile_ThrowsException() throws IOException {
// Arrange
String secretValue = "secret";
doThrow(new IOException("IO error")).when(mockWriter).write(anyString());
// Act & Assert
Exception exception = assertThrows(RuntimeException.class, () -> myHelper.saveSecretToFile(secretValue));
assertTrue(exception.getCause() instanceof IOException);
assertEquals("Error writing to file", exception.getMessage());
verify(mockWriter, times(1)).write(secretValue); // Проверка, что метод write был вызван перед исключением
}
}
Пояснение к тестам:
-
Тест успеха (
testSaveSecretToFile_Success
):- Проверяет, что метод
write
был вызван один раз с правильным значением. - Проверяет, что метод
flush
был также вызван.
- Проверяет, что метод
-
Тест на выбрасывание исключения (
testSaveSecretToFile_ThrowsException
):- Настраивает
mockWriter
так, чтобы при вызовеwrite
выбрасывалосьIOException
. - Проверяет, что в результате вызова метода будет возбуждено
RuntimeException
, и в качестве причины этого исключения будетIOException
. - Убедитесь, что метод
write
был вызван один раз перед тем, как исключение было выброшено.
- Настраивает
Следуя этим шагам, вы сможете корректно протестировать свой метод saveSecretToFile
, включая сценарии, когда происходит ошибка.