Jasmine предоставляет мощные инструменты для тестирования, включая возможность создания мок-объектов. Мок-объекты позволяют имитировать работу зависимостей, которые могут быть слишком сложными, медленными или непредсказуемыми для тестирования в реальных условиях. Manual mocking — это техника, при которой мок-объекты создаются вручную, а не с помощью встроенных инструментов или библиотек. Эта техника позволяет более гибко контролировать поведение зависимостей, улучшая изоляцию тестируемого кода.
Manual mocking включает создание вручную имитаций зависимостей с
конкретным поведением, которое требуется для тестирования. Это
отличается от автоматических методов, таких как использование
spyOn, когда поведение объекта заменяется автоматически.
Manual mocking позволяет более точно контролировать, как зависимости
взаимодействуют с тестируемым кодом, а также предоставляет возможность
воссоздавать специфичные сценарии.
Изоляция тестов. Важно, чтобы тестируемый код не зависел от внешних факторов, таких как запросы к базе данных, сторонние сервисы или сложные API. Моки изолируют тестируемую функцию от этих зависимостей, позволяя сосредоточиться только на проверке логики.
Снижение сложности. Некоторые зависимости могут быть слишком сложными для тестирования, например, объекты с множеством методов или асинхронным поведением. Моки позволяют упростить эти объекты до уровня, необходимого для теста.
Повторяемость тестов. Мок-объекты помогают создать стабильную среду для тестирования, исключая случайные сбои или зависимости от внешних сервисов. Это делает тесты более предсказуемыми.
Для создания моков вручную в Jasmine можно использовать простые JavaScript-объекты с нужным набором методов и свойств. Рассмотрим пример, где требуется замокать HTTP-запросы.
Предположим, существует модуль, который делает HTTP-запросы:
function ApiClient() {
this.getData = function(url) {
return fetch(url).then(response => response.json());
};
}
Для тестирования этого модуля можно создать мок-объект, который будет имитировать выполнение запроса, не вызывая реальных HTTP-запросов:
describe('ApiClient', function() {
it('should return mocked data', function() {
// Создаем мок-объект
const mockApiClient = {
getData: function(url) {
return Promise.resolve({ data: 'mocked data' });
}
};
// Используем мок в тестируемой функции
mockApiClient.getData('https://example.com').then(response => {
expect(response.data).toBe('mocked data');
});
});
});
В данном примере метод getData замокан так, чтобы сразу
возвращать промис с фальшивыми данными, что позволяет протестировать
остальную логику без зависимости от реального HTTP-запроса.
Точная настройка. Manual mocking предоставляет полный контроль над поведением зависимостей. Вы можете задать моки с любым поведением, включая ошибки, задержки или специфичные данные, что важно для тестирования крайних случаев.
Минимизация сторонних зависимостей. В случае с моками нет необходимости устанавливать дополнительные библиотеки или инструменты, все ограничивается только JavaScript-объектами.
Гибкость. Моки могут быть настроены на возврат разных значений в зависимости от условий, параметров или предыдущих вызовов, что позволяет тестировать различные сценарии.
Иногда тестируемая зависимость может быть сложной и включать несколько методов. Например, если у нас есть сервис, который выполняет несколько различных операций:
function DataService() {
this.fetchData = function() {
return fetch('data.json').then(response => response.json());
};
this.saveData = function(data) {
return fetch('save.json', {
method: 'POST',
body: JSON.stringify(data)
});
};
}
Для тестирования этой зависимости вручную, можно создать мок с несколькими методами:
describe('DataService', function() {
it('should fetch data correctly', function() {
const mockDataService = {
fetchData: function() {
return Promise.resolve({ id: 1, name: 'test' });
},
saveData: function(data) {
return Promise.resolve({ success: true });
}
};
mockDataService.fetchData().then(response => {
expect(response.id).toBe(1);
expect(response.name).toBe('test');
});
mockDataService.saveData({ id: 2, name: 'new data' }).then(response => {
expect(response.success).toBe(true);
});
});
});
Здесь мок-объект mockDataService имитирует два метода —
fetchData и saveData, каждый из которых
возвращает заданные значения. Это позволяет протестировать логику, не
зависимо от реальных API-запросов.
Иногда требуется более сложное поведение мока, например, когда один метод должен вернуть разные значения при нескольких вызовах. Для этого можно использовать более сложную логику в моках:
describe('UserService', function() {
it('should call fetchUserData multiple times with different results', function() {
const mockUserService = {
fetchUserData: jasmine.createSpy('fetchUserData')
.and.callFake(function(userId) {
if (userId === 1) {
return Promise.resolve({ id: 1, name: 'John' });
} else if (userId === 2) {
return Promise.resolve({ id: 2, name: 'Jane' });
} else {
return Promise.reject('User not found');
}
})
};
mockUserService.fetchUserData(1).then(response => {
expect(response.name).toBe('John');
});
mockUserService.fetchUserData(2).then(response => {
expect(response.name).toBe('Jane');
});
mockUserService.fetchUserData(3).catch(error => {
expect(error).toBe('User not found');
});
});
});
Здесь используется метод jasmine.createSpy, который
позволяет записывать вызовы функции и контролировать ее поведение. В
данном случае мок-метод fetchUserData возвращает разные
данные в зависимости от переданного userId.
Избыточность кода. Ручное создание моков может привести к большому количеству дублирующегося кода, особенно когда зависимостей много. Это увеличивает трудоемкость тестов, особенно в случае крупных проектов.
Поддержка моков. Моки требуют тщательной поддержки, так как они могут устаревать вместе с изменениями в тестируемом коде. Изменения в API или бизнес-логике могут потребовать обновления моков.
Отсутствие интеграционного тестирования. Используя моки, вы тестируете только логику внутри тестируемой единицы. Реальное взаимодействие с внешними сервисами или компонентами не проверяется, что может привести к проблемам в реальной работе.
Manual mocking — это мощная техника для создания изолированных тестов, когда необходимо контролировать зависимости и поведение сторонних объектов. Эта техника предоставляет гибкость и точность, но требует внимательного подхода к поддержке тестов, особенно в крупных проектах. Несмотря на свои ограничения, она является неотъемлемой частью тестирования в Jasmine, позволяя создавать надежные и изолированные юнит-тесты.