В этой статье мы увидим JUnit и Mockito в действии с использованием двух компонентов Java, класса службы и класса репозитория.
Мы начнем с создания классов, а затем напишем тесты по-разному, используя такие понятия, как утверждение, проверка, проверка на выброшенное исключение, ArgumentMatcher и ArgumentCaptor. Наконец, мы создадим более чистые тесты, извлекая дублированный код и даже используя аннотации Mockito. Мы не будем фокусироваться на 100% покрытии кода.
Тестируемый код
DataRepository.java
public interface DataRepository {
int[] retrieveAllData();
int getStoredSumById(int id);
void save(Object o);
}
DataService.java
public interface DataService {
int calculateSum();
void setDataRepository(DataRepository dataRepository);
int calculateNewSum(int id);
void save(Data o);
}
DataServiceImpl.java
public class DataServiceImpl implements DataService {
private DataRepository dataRepository;
public void setDataRepository(DataRepository dataRepository) {
this.dataRepository = dataRepository;
}
public int calculateSum(){
int sum = 0;
for(int value : dataRepository.retrieveAllData()){
sum += value;
}
return sum;
}
public int calculateNewSum(int id){
int sum = dataRepository.getStoredSumById(id);
return sum + sum;
}
public void save(Data o){
o = new Data(o.getName().toUpperCase());
dataRepository.save(o);
}
}
Данные.java
public class Data {
private String name;
// constructors, getters and setters
}
Модульные тесты без аннотаций Mockito
Тест для метода calculateSum() из класса обслуживания, фиктивный репозиторий. В этом тесте мы предполагаем, что фиктивный метод retrieveAllData() из репозитория возвращает массив с данными.
@Test
public void calculateSum_Should_ReturnResult_When_DataIsProvided() {
//create service under test
DataService ms = new DataServiceImpl();
//mock repository to test service in isolation
DataRepository dataRepositoryMock = mock(DataRepository.class);
when(dataRepositoryMock.retrieveAllData())
.thenReturn(new int[]{1, 2, 3});
//set mock to service
ms.setDataRepository(dataRepositoryMock);
//call method under test
int result = ms.calculateSum();
//verify if method on the mock is called by service under test
//it is mostly used when a method that is called on a mock
//does not have a return
verify(dataRepositoryMock, times(1)).retrieveAllData();
//assert result
assertEquals(6, result);
}
Тест для метода calculateSum() из класса обслуживания, фиктивный репозиторий. Мы предполагаем, что фиктивный метод retrieveAllData() из репозитория возвращает массив без данных.
@Test
public void calculateSum_Should_ReturnZero_When_DataIsEmpty() {
//create service under test
DataService ms = new DataServiceImpl();
//mock repository to test service in isolation
DataRepository dataRepositoryMock = mock(DataRepository.class);
when(dataRepositoryMock.retrieveAllData())
.thenReturn(new int[]{});
//set mock to service
ms.setDataRepository(dataRepositoryMock);
//call method under test
int result = ms.calculateSum();
//verify if method on the mock is called by service under test
verify(dataRepositoryMock, times(1)).retrieveAllData();
//assert result
assertEquals(0, result);
}
Тест для метода calculateSum() из класса обслуживания, фиктивный репозиторий. Предполагая, что имитированный метод retrieveAllData() из репозитория возвращает значение null, в результате чего метод calculateSum() из класса обслуживания выдает исключение NullPointerException.
@Test
public void calculateSum_Should_ThrowException_When_DataIsNull() {
assertThrows(NullPointerException.class, () -> {
//create service under test
DataService ms = new DataServiceImpl();
//mock repository to test service in isolation
DataRepository dataRepositoryMock =
mock(DataRepository.class);
when(dataRepositoryMock.retrieveAllData()).thenReturn(null);
//set mock to service
ms.setDataRepository(dataRepositoryMock);
//call method under test
ms.calculateSum();
});
}
Протестируйте метод calculateNewSum() из класса обслуживания, имитируя репозиторий с помощью ArgumentMatchers. В этом тесте мы предполагаем, что фиктивный метод getStoredSumById(‹вызывается с любым Integer›) из репозитория возвращает 2. Новая тема, представленная здесь, — это ArgumentMatchers, такие как any(), anyInt() и т. д., которые можно использовать для замены фактический аргумент, чтобы сделать тесты более общими.
@Test
void calculateNewSum_Should_ReturnResult_When_DataIsProvided() {
//create service under test
DataService ms = new DataServiceImpl();
//mock repository to test service in isolation
DataRepository dataRepositoryMock = mock(DataRepository.class);
//return 2 when method is called with any int value
when(dataRepositoryMock.getStoredSumById(anyInt()))
.thenReturn(2);
//set mock to service
ms.setDataRepository(dataRepositoryMock);
//call method under test
int result = ms.calculateNewSum(1);
//verify if method on the mock is called by
//service under test with any argument
verify(dataRepositoryMock, times(1)).getStoredSumById(anyInt());
//assert result
assertEquals(4, result);
}
Тест на метод save() из класса обслуживания, имитирующий репозиторий. В этом случае метод save() из класса сервиса ничего не возвращает для подтверждения. Мы можем использовать ArgumentCaptor, чтобы проверить, вызывается ли метод save() из класса репозитория с ожидаемыми аргументами.
@Test
void save_ShouldCallRepository_With_GivenParam() {
// create service under test
DataService ms = new DataServiceImpl();
// mock repository to test service in isolation
DataRepository dataRepositoryMock = mock(DataRepository.class);
// set mock to service
ms.setDataRepository(dataRepositoryMock);
// call method under test
Data o = new Data("MockitoObject");
ms.save(o);
//create expected object
Data expected = new Data("MOCKITOOBJECT");
// because the method does not return anything we can check
// if mock method was called with an expected parameter
ArgumentCaptor<Data> captor =
ArgumentCaptor.forClass(Data.class);
verify(dataRepositoryMock, times(1)).save(captor.capture());
//assert captured argument
assertEquals(expected, captor.getValue());
}
Более чистые модульные тесты без аннотаций Mockito
Мы могли бы извлечь повторяющийся код из каждого теста во внешние методы, как в примере ниже, чтобы сделать наш код чище.
public class DataServiceTest_Clean {
//create service under test
DataService ms = new DataServiceImpl();
//mock repository to test service in isolation
DataRepository dataRepositoryMock = mock(DataRepository.class);
@BeforeEach
public void beforeEach(){
//set mock to service
ms.setDataRepository(dataRepositoryMock);
}
@Test
public void calculateSum_Should_ReturnResult_When_Data() {
//mock repository to test service in isolation
when(dataRepositoryMock.retrieveAllData())
.thenReturn(new int[]{1, 2, 3});
//call method under test
int result = ms.calculateSum();
//verify if method on the mock is called by
//service under test
//it is mostly used when a method that is
//called on a mock does not have a return
verify(dataRepositoryMock, times(1)).retrieveAllData();
//assert result
assertEquals(6, result);
}
}
Модульные тесты с аннотациями Mockito
В приведенном выше примере мы продемонстрировали, как мы можем удалить дублированный код из нашего теста, но мы можем сделать шаг вперед и использовать аннотации Mockito для создания макетов и внедрения их с помощью аннотаций.
@ExtendWith(MockitoExtension.class)
public class DataServiceTest_Clean_WithAnnotations {
//create service under test and inject all mocks needed
//there is no need to manually inject
//mockitoRepositoryMock, just create it with @Mock
@InjectMocks
DataServiceImpl ms;
//mock repository to test service in isolation
@Mock
DataRepository dataRepositoryMock;
@Test
void calculateSum_Should_ReturnResult_When_DataIsProvided() {
//mock repository to test service in isolation
when(dataRepositoryMock.retrieveAllData())
.thenReturn(new int[]{1, 2, 3});
//call method under test
int result = ms.calculateSum();
//verify if method on the mock is called
//by service under test
//it is mostly used when a method that is called
//on a mock does not have a return
verify(dataRepositoryMock, times(1)).retrieveAllData();
//assert result
assertEquals(6, result);
}
}
Код доступен через GitHub.
Подробнее о тестировании
Для более сложного примера вы можете проверить этот проект на GitHub.
Первоначально опубликовано на https://weinspire.tech 10 марта 2020 г.