#angular #typescript #unit-testing #karma-jasmine
#angular #typescript #модульное тестирование #karma-jasmine
Вопрос:
Я внес некоторые обновления в компонент Angular, и это нарушило некоторые модульные тесты. Все тестовые спецификации нарушены, поэтому мне кажется, что это как-то связано с инициализацией в beforeEach
вызовах. Я пытался тщательно исследовать проблему, но не добился никакого успеха.
Ошибка, которую я получаю после запуска своих модульных тестов, заключается в: TypeError: Cannot read property 'subscribe' of undefined
Единственными вызовами, выполняемыми в subscribe
, являются:
this.ruleDataFieldOption$.subscribe
...
this.submitted$.subscribe
...
this.dataFieldControl.valueChanges.subscribe
Я попытался явно инициализировать, ruleDataFieldOption$
установив для него значение of(mockRuleDataFields)
, но это не сработало. Я также попытался переместить блок кода из второго beforeEach
вызова в обещание асинхронного beforeEach
вызова (т.е. .compileComponents().then( () => {...} )
и это тоже не сработало.
client-rules-condition.component.spec.ts
const mockDataFieldService = {
getRuleDataFields: () => {}
} as RuleDataFieldService;
const mockStore = {
select: td.function('.select'),
dispatch: td.function('.dispatch'),
pipe: td.function('.pipe'),
} as Store<any>;
let dispatch;
let pipe;
const mockConfigService = {
getConfiguration: td.func(() => {
return mockNotificationConfig
})
} as ConfigService;
const mockNotificationConfig = {
Env: null,
apiBaseUrl: 'blah'
} as MclNotificationConfig;
const mockRuleDataFields: RuleDataFieldDefinition[] = [
{name: 'data field 1', dataFieldId: 'mock data field guid', category: 'mock category'} as RuleDataFieldDefinition
];
fdescribe('ClientRulesConditionComponent', () => {
let component: ClientRulesConditionComponent;
let fixture: ComponentFixture<ClientRulesConditionComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ClientRulesConditionComponent ],
imports: [ReactiveFormsModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{provide: Store, useFactory: () => mockStore},
{provide: RuleDataFieldService, useValue: mockRuleDataFields},
HttpClientTestingModule,
// {provide: ConfigService, useFactory: () => mockConfigService},
// {provide: MclNotificationConfig, useFactory: () => mockNotificationConfig},
// HttpHandler, HttpClient
]
})
.compileComponents();
}));
beforeEach(() => {
dispatch = spyOn(mockStore, 'dispatch');
pipe = spyOn(mockStore, 'pipe').and.callThrough();
fixture = TestBed.createComponent(ClientRulesConditionComponent);
component = fixture.componentInstance;
const fb: FormBuilder = new FormBuilder();
component.conditionForm = fb.group({
name: fb.control('', [Validators.required]),
triggerWhen: fb.control('', [Validators.required]),
conditions: fb.array([
fb.group({
dataField: fb.control('df1'),
conditionToMatch: fb.control('ctm1'),
value: fb.control('v1')
}),
fb.group({
dataField: fb.control('df2'),
conditionToMatch: fb.control('ctm2'),
value: fb.control('v2')
})
])
});
component.indexInConditionArray = 1;
component.submitted$ = of(false);
component.ruleDataFieldOption$ = of(mockRuleDataFields);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
// Confirm conditionToMatch and dataField get actions have been dispatched
expect(dispatch).toHaveBeenCalledTimes(2);
// Confirm that we load the correct set of form controls and they have the correct value associated with them
const conditionsArray = component.conditionForm.controls['conditions'] as FormArray;
const conditionGroup = conditionsArray.at(component.indexInConditionArray) as FormGroup;
expect(component.dataFieldControl).toEqual(conditionGroup.controls['dataField'] as FormControl);
expect(component.dataFieldControl.value).toEqual('df2');
expect(component.matchingConditionControl).toEqual(conditionGroup.controls['conditionToMatch'] as FormControl);
expect(component.matchingConditionControl.value).toEqual('ctm2');
expect(component.valueControl).toEqual(conditionGroup.controls['value'] as FormControl);
expect(component.valueControl.value).toEqual('v2');
// Confirm the conditionToMatch and dataField selectors were wired up
expect(pipe).toHaveBeenCalledTimes(2);
// Confirm the expected value for the 'submitted' flag got emitted
expect(component.submitted).toBeFalsy();
});
});
.
.
.
client-rules-condition.component.ts
@Component({
selector: 'nd-client-rules-condition',
templateUrl: './client-rules-condition.component.html',
styleUrls: ['./client-rules-condition.component.scss']
})
export class ClientRulesConditionComponent implements OnInit {
@Input() conditionForm: FormGroup;
@Input() indexInConditionArray: number;
@Input() submitted$: Observable<boolean>;
@Output() removeCondition: EventEmitter<number> = new EventEmitter();
conditionToMatchOption$: Observable<ConditionToMatch[]>;
valueControl: FormControl;
matchingConditionControl: FormControl;
dataFieldControl: FormControl;
private static NPI_DATA_FIELD_GUID: string;
validValuePattern: string;
invalidValueError: string;
// Emits the candidate rule fields that eventually come back from the database.
// The 'data fields' combo box in the markup is wired up to this.
ruleDataFieldOption$: Observable<RuleDataFieldDefinition[]>;
// Tracks whether the parent form has been submitted.
submitted: boolean;
constructor(private _store: Store<state.AppState>) { }
ngOnInit() {
this._store.dispatch(new GetConditionToMatchOptions(''));
// Dispatch Action that causes the rule-data-field options to get retrieved from the server and slammed into the global state.
this._store.dispatch(new GetRuleDataFieldsOptions(''));
this.conditionToMatchOption$ = this._store.pipe(select(getConditionToMatchOption));
// Wire up the selector that cause the rule-data-field options to get pulled from the global state when they are set/updated
this.ruleDataFieldOption$ = this._store.pipe(select(getRuleDataFieldsSelector));
// Load GUID for NPI field to use to distinguish between different Data Field dropdown items to apply different validation in Value To Match
this.ruleDataFieldOption$.subscribe(dataFields => {
if (ClientRulesConditionComponent.NPI_DATA_FIELD_GUID == null) {
for (let df of dataFields) {
if (df.name.toLowerCase().includes('npi') || df.name.toLowerCase().includes('national provider identifier')) {
ClientRulesConditionComponent.NPI_DATA_FIELD_GUID = df.dataFieldId.toUpperCase();
break;
}
}
}
});
.
.
.
Я уверен, что это что-то незначительное, и мне просто нужно, чтобы мои модульные тесты прошли.
Комментарии:
1. Слишком много кода, но первое, что появляется,
spyOn(mockStore, 'dispatch')
вероятно, должно возвращатьObservable
.2. Я согласен с @The Head Rush, это довольно много кода, не имеющего отношения к реальной проблеме. Но я не думаю, что это шпион отправки, поскольку это просто вызываемый метод. Но, скорее всего, select spy не возвращает наблюдаемое. И вам нужно знать, что при первом fixture.DetectChanges вызывается ngOnInit. Итак, вы устанавливаете ruleDataField $ с фиксированным значением, а затем вызываете OnInit, и оно заменяется вашим select spy, который возвращает только null.
Ответ №1:
Попробуйте это, в одном из перед каждым declare
новым observable
.
component.ruleDataFieldOption$ = new Observable<ConditionToMatch[]>;
Я не знаю, применяются ли те же правила к наблюдаемому it self, что и к объекту, но вы можете попробовать.