Вот обычный старый компонент Angular:
@Component({ selector: 'app-dog', templateUrl: './dog.component.html', styleUrls: ['./dog.component.css'] }) class DogComponent { constructor(private logger) {} speak() { this.logger.info('bark'); } }
Самый распространенный способ (то есть самый простой способ после запуска ng generate
) протестировать такой компонент — использовать TestBed
describe('DogComponent' () => { let component: BannerComponent; let fixture: ComponentFixture<BannerComponent>; let mockLogger; let logger; beforeEach(async(() => { // using jest here, but any testing library will do mockLogger = { info: jest.fn() }; TestBed.configureTestingModule({ declarations: [ DogComponent ], providers: [ { provide: Logger, useValue: mockLogger } ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BannerComponent); component = fixture.componentInstance; fixture.detectChanges(); logger = TestBed.get(Logger); }); describe('speak', () => { component.speak(); expect(logger.info).toHaveBeenCalledWith('bark'); }); });
К сожалению, легко впасть в настройку TestBed
перед каждой спецификацией компонента, даже если это бесполезно. Для большого компонента с множеством импортов, объявлений и провайдеров это может означать сотни миллисекунд для каждой спецификации. В моей работе эти спецификации часто занимали до 300 мс каждая. Они складываются и могут разрушить циклы обратной связи с разработчиками.
Основы TypeScript в помощь
Вот пример теста, выполнение которого занимает около 1 миллисекунды:
const component = { logger: { info: jest.fn() } }; const speak = DogComponent.prototype.speak.bind(component); speak(); expect(component.logger.info).toHaveBeenCalledWith('bark');
Давайте сломаем это
component
— это объект, очень похожий на настоящий DogComponent
. У него есть logger
, где info
замаскирован, но нет метода speak
.
const component = { logger: { info: jest.fn() } };
На самом деле, просто достаточно this
контекста для метода speak
. Единственный раз, когда speak
ссылается на this
, это когда он вызывает this.logger.info('bark')
. Так что это все, что нам нужно предоставить.
const speak = DogComponent.prototype.speak.bind(component);
Мы делаем функцию speak
. Это то же самое, что и метод speak
для DogComponent
, за исключением того, что мы вызываем bind
, поэтому каждый this
в методе заменяется на component
.
Понимание методов TypeScript
Методы и классы в TypeScript/ES6 могут не соответствовать вашим ожиданиям.
class DogComponent { constructor(private logger) {} speak() { this.logger.info('bark'); } }
Действительно ли синтаксический сахар для
function DogComponent(logger) { this.logger = logger; } DogComponent.prototype.speak = function() { this.logger.info('bark'); }
Когда мы устанавливаем атрибут прототипа speak
, каждый экземпляр DogComponent
будет автоматически связывать этот метод с самим собой.
И обратите внимание, как мы взаимодействуем с этим в нашем тесте:
// in built source DogComponent.prototype.speak = function() { this.logger.info('bark'); } // in test (call bind so that 'this' references 'component') const speak = DogComponent.prototype.speak.bind(component);
Альтернативно
Еще один способ сделать такие же быстрые тесты — new
объединить ваши компоненты.
const logger = { info: mockFn() }; const dog = new Dog(logger); dog.speak(); expect(logger.info).toHaveBeenCalledWith('bark');
Хорошая практика
Если вы решили протестировать метод, извлекая его из прототипа, вы тестируете этот метод изолированно. Это хорошая вещь! Это позволяет очень легко проверить, как этот метод взаимодействует с объектом, к которому он привязан. Но это не замена интеграционному тестированию и DOM-тестированию. Обязательно проверьте поведение, которое вы хотите — для этого могут потребоваться интегрированные тесты, требующие дополнительной настройки. Логика шаблона также может легко потребовать тестирования DOM.
Не сбрасывайте со счетов TestBed
Хотя TestBed
является источником многих излишне медленных тестов, это все же отличный инструмент. Если вы тестируете только методы компонента, вы не можете сказать, что передается дочерним компонентам, вы не можете проверить, что происходит в DOM, и вы упускаете удобный TestBed
API.