В реальной жизни
Для большего контроля над зависимостями и упрощения тестирования DIP предлагает использовать инверсию управления (Inversion of Control, IoC) и инъекцию зависимостей (Dependency Injection, DI).
DIP, DI и тестирование
Писать тесты без DIP возможно, но следуя принципу это делать проще. Если модуль зависит от абстракции, то его зависимость легче подменить фейковым объектом.
Вернёмся к примеру с аутентификацией. Для того, чтобы протестировать реализацию, которая не следует DIP, нам придётся создать экземпляр класса MySqlConnection
:
describe('Auth', () => {
let connection: MySqlConnection
let auth: Auth
beforeEach(() => {
connection = new MySqlConnection(/*...*/)
auth = new Auth(connection)
})
it('should successfully authenticate user', async () => {
const result: AuthResult = await auth.authenticate('Alex', '123')
expect(result.status).toEqual(200)
})
})
Проблема — в производительности. Хороший модульный тест должен быть быстрым, а создавать экземпляр настоящего объекта на каждый тест — ресурсозатратно.
Кроме того, в плохой реализации класс MySqlConnection
сам может зависеть от других модулей, экземпляры которых тоже будут создаваться. Всё это сильно тормозит тесты.
Теперь попробуем протестировать реализацию, которая следует DIP:
class DbMock implements DataBaseConnection {
connect(host: string, user: string, password: string): void {
// Здесь дешёвые операции-заглушки,
// подключаться к базе совсем не обязательно.
}
// Тут реализация только тех методов,
// которые понадобятся для тестов.
}
describe('Auth', () => {
let connection: DataBaseConnection
let auth: Auth
beforeEach(() => {
connection = new DbMock(/*...*/)
auth = new Auth(connection)
})
// ...
})
Класс DbMock
реализует интерфейс DataBaseConnection
. Мы можем спроектировать его максимально простым и лёгким, это ускорит работу теста.
Инверсия управления
DI — это частный случай инверсии управления. При этом подходе контроль за выполнением программы отдаётся фреймворку, который знает, в какой момент и какую функцию надо вызвать. Цель IoC — сделать систему расширяемой.
Inversify предлагает решение для IoC на TypeScript. Inversify предоставляет API для создания контейнеров с указанием зависимостей, которые потом подставляются автоматически.