#unit-testing #angular
#модульное тестирование #angular
Вопрос:
В моем приложении есть LogComponent. Внутри ngOnInit я извлекаю журналы с сервера через LogService и отображаю их в виде сетки.
Вот 2 способа, как это сделать:
1-й:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Log } from './log';
import { LogService } from './log.service';
import * as globals from '../../globals';
import { DataTransformService, HttpHandlerService, StateManagerService } from '../../shared';
@Component({
selector: 'app-log',
templateUrl: 'log.component.html',
styleUrls: ['log.component.css'],
providers: [ DataTransformService, HttpHandlerService, LogService, StateManagerService]
})
export class LogComponent implements OnInit {
localGlobals: any = globals;
logs: Log[];
currentPage: number;
totalRecords: number = 0;
firstRowIndex: number = 0;
constructor(
private stateManagerService: StateManagerService,
private httpHandlerService: HttpHandlerService,
private logService: LogService,
private dataTransformService: DataTransformService,
private router: Router
) {}
ngOnInit() {
//get page number from local storage
this.currentPage = this.stateManagerService.getParamFromState(this.router.url, 'page');
this.firstRowIndex = this.currentPage * this.localGlobals.ROWS_PER_PAGE - this.localGlobals.ROWS_PER_PAGE;
//get total count
let respHandler = (res: any) => {
this.totalRecords = res.headers.get('X-Pagination-Total-Count');
return this.httpHandlerService.extractData(res);
};
this.logService.search(this.currentPage, respHandler).subscribe(
logs => {
this.logs = logs;
},
err => console.error(err)
);
}
}
Первый подход использует эту функцию для извлечения журналов:
this.logService.search(this.currentPage, respHandler).subscribe(
logs => {
this.logs = logs;
},
err => console.error(err)
);
Второй способ получения журналов — через Observable.forkJoin:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Log } from './log';
import { LogService } from './log.service';
import * as globals from '../../globals';
import { DataTransformService, HttpHandlerService, StateManagerService } from '../../shared';
@Component({
selector: 'app-log',
templateUrl: 'log.component.html',
styleUrls: ['log.component.css'],
providers: [ DataTransformService, HttpHandlerService, LogService, StateManagerService]
})
export class LogComponent implements OnInit {
localGlobals: any = globals;
logs: Log[];
currentPage: number;
totalRecords: number = 0;
firstRowIndex: number = 0;
constructor(
private stateManagerService: StateManagerService,
private httpHandlerService: HttpHandlerService,
private logService: LogService,
private dataTransformService: DataTransformService,
private router: Router
) {}
ngOnInit() {
//get page number from local storage
this.currentPage = this.stateManagerService.getParamFromState(this.router.url, 'page');
this.firstRowIndex = this.currentPage * this.localGlobals.ROWS_PER_PAGE - this.localGlobals.ROWS_PER_PAGE;
//get total count
let respHandler = (res: any) => {
this.totalRecords = res.headers.get('X-Pagination-Total-Count');
return this.httpHandlerService.extractData(res);
};
Observable.forkJoin(
this.logService.search(this.currentPage, respHandler)
).subscribe(
data => {
this.logs = data[0];
},
err => console.error(err)
);
}
}
Второй подход использует эту функцию для извлечения журналов:
Observable.forkJoin(
this.logService.search(this.currentPage, respHandler)
).subscribe(
data => {
this.logs = data[0];
},
err => console.error(err)
);
У меня есть тест для этого компонента, который проверяет, инициализируются ли свойства после инициализации:
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { ReplaySubject } from 'rxjs/Rx';
import { Log } from './log';
import { LogComponent } from './log.component';
import { LogService } from './log.service';
import { DataTransformService, HttpHandlerService, LocalStorageService, StateManagerService } from '../../shared';
let comp: LogComponent;
let fixture: ComponentFixture<LogComponent>;
let de: DebugElement;
describe('LogComponent', () => {
// async compile html and css
beforeEach(async(() => {
let project = new ReplaySubject(1);
let logServiceStub = {
search: (pageNum: number, respHandler: any) => {
let logs = [
{ id: 1, ip: '127.0.0.1', user_id: 1, notification_event_id: 1, created_at: 1 },
{ id: 2, ip: '127.0.0.2', user_id: 2, notification_event_id: 2, created_at: 2 }
];
project.next(logs);
return project;
}
};
let routerStub = {
url: 'log_url'
};
TestBed.configureTestingModule({
declarations: [ LogComponent ],
imports: [ FormsModule ],
providers: [
DataTransformService,
HttpHandlerService,
LocalStorageService,
{ provide: Router, useValue: routerStub },
StateManagerService
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.overrideComponent(LogComponent, {
set: {
providers: [
{ provide: LogService, useValue: logServiceStub }
]
}
})
.compileComponents();
}));
// synchronous
beforeEach(() => {
fixture = TestBed.createComponent(LogComponent);
// LogComponent test instance
comp = fixture.componentInstance;
de = fixture.debugElement;
});
it('initial variables are set after init', () => {
fixture.detectChanges();
console.log(comp.logs);
//TODO: test not working when requests are in forkJoin
expect(comp.currentPage).toBe(1);
expect(comp.firstRowIndex).toBe(0);
expect(comp.logs).not.toBeUndefined();
expect(comp.logs.length).toBe(2);
});
});
Проблема: когда я запускаю тест с использованием 1-го подхода (без Observable.forkJoin) работает нормально. НО когда я запускаю тест, используя 2-й подход (с наблюдаемым.forkJoin) завершается ошибкой:
Ожидаемый неопределенный не должен быть неопределенным.
Таким образом, журналы не инициализируются после fixture.DetectChanges() . Похоже, что запрос находится внутри наблюдаемого.forkJoin() тогда инициализация не выполняется. Я понимаю, что в Observable есть только 1 запрос.forkJoin() и я могу использовать 1-й подход, но у меня есть другие компоненты в системе, которые используют более 1 запроса, поэтому мне нужно решить эту проблему.
Надеюсь, вы поняли, что я имею в виду.
Заранее спасибо.
Комментарии:
1. Я также сталкиваюсь с этой проблемой. Вы нашли какое-либо решение для этого? @ryzhak
2. Я также ищу ответ на этот вопрос
3. @ryzhak не могли бы вы принять мой ответ ниже?
Ответ №1:
Хорошо, ребята, после некоторых усилий я обнаружил, что observable необходимо завершить forkJoin
, чтобы его перехватить.
Поэтому, если вы вызываете
project.next(something)
этого недостаточно.
Вам также необходимо вызвать:
project.complete()
чтобы заставить его работать!
Имейте в виду, что вы всегда можете использовать zip
вместо forkJoin
для обработки .next
распространения, а также .complete
Комментарии:
1. Это тоже решило мою проблему, хотя я должен добавить небольшое замечание: complete следует вызывать сразу после next для каждого предмета, иначе это не сработает.
Ответ №2:
У меня была аналогичная проблема, и ее удалось решить после включения утверждения в функцию setTimeout.
setTimeout(() => { expect(mockService.functionName).toHaveBeenCalledTimes(1); }, 10);