英文:
Unit Testing - Mocking a Subject using ng-mocks
问题
I am trying to add unit tests to my angular component which is dependent on a service. The component is subscribing to a flag variable in the service. I am trying to use ng-mocks to mock the service using the MockProvider. And while trying to set the default mock behavior in the test.ts file, I am getting an error, which I couldn't make sense of.
"The last overload gave the following error.
Argument of type 'typeof SomeService' is not assignable to parameter of type 'AnyDeclaration<{ flags: Observable
Below is the code.
Any other approach to mock the subject and unit test the component. If I am not initializing the flagChange to EMPTY in the test.ts, I am getting the "Cannot read properties of undefined (reading 'subscribe')" Error.
Service.ts
import { Injectable, OnDestroy } from '@angular/core';
import * as LDClient from 'launchdarkly-js-client-sdk';
import { LDFlagValue } from 'launchdarkly-js-client-sdk';
import { Observable, Subject, of } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class SomeService implements OnDestroy {
ldClient!: LDClient.LDClient;
flags: LDClient.LDFlagSet;
flagChange!: Subject<Object>;
private readonly _destroying$ = new Subject<void>();
ldClientId: string;
constructor(private globalvars: GlobalVars) {
this.ldClientId = 'client-id';
this.flags = {
'allow_edit': false,
};
}
ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
}
initializeLD() {
const context = {
kind: 'user',
key: 'my-key',
};
this.ldClient = LDClient.initialize(this.ldClientId, context);
this.ldClient.on('initialized', () => {
const boolFlagValue = this.ldClient.variation(
'allow_edit'
) as boolean;
this.flags['allow_edit'] = boolFlagValue;
this.flagChange.next(this.flags);
});
this.ldClient.on('change', (allChanges) => {
this.flags['allow_edit'] =
allChanges['allow_edit'].current;
this.flagChange.next(this.flags);
});
this.ldClient.on('ready', () => {
const boolFlagValue = this.ldClient.variation(
'allow_edit'
) as boolean;
});
}
}
test.ts
ngMocks.defaultMock(SomeService , () => ({
flags: EMPTY,
flagChange: EMPTY //Have declared it as "flagChange: ()=> EMPTY" as well
}));
component.ts
constructor(private featureFlag: SomeService) {
this.allow_edit= featureFlag.flags['allow_edit'];
this.allow_edit= featureFlag.flagChange.subscribe((flags:any) => {
this.allow_edit= flags['allow_edit'];
});
}
spec.ts
fdescribe('SomeComponent', () => {
let component: SomeComponent;
let fixture: ComponentFixture<SomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HttpClientModule],
declarations: [SomeComponent],
providers: [
MockProvider(SomeService),
],
}).compileComponents();
fixture = TestBed.createComponent(SomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
英文:
I am trying to add unit tests to my angular component which is dependent on a service. The component is subscribing to a flag variable in the service. I am trying to use ng-mocks to mock the service using the MockProvider. And while trying to set the default mock behaviour in the test.ts file, am getting an error, which I couldn't make sense of.
"The last overload gave the following error.
Argument of type 'typeof SomeService' is not assignable to parameter of type 'AnyDeclaration<{ flags: Observable<never>; flagChange: Observable<never>; }>[]'."
Below is the code.
Any other approach to mock the subject and unit test the component. If I am not initializing the flagChange to EMPTY in the test.ts, I am getting the "Cannot read properties of undefined (reading 'subscribe')" Error.
Service.ts
import { Injectable, OnDestroy } from '@angular/core';
import * as LDClient from 'launchdarkly-js-client-sdk';
import { LDFlagValue } from 'launchdarkly-js-client-sdk';
import { Observable, Subject, of } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class SomeService implements OnDestroy {
ldClient!: LDClient.LDClient;
flags: LDClient.LDFlagSet;
flagChange!: Subject<Object>;
private readonly _destroying$ = new Subject<void>();
ldClientId: string;
constructor(private globalvars: GlobalVars) {
this.ldClientId = 'client-id';
this.flags = {
'allow_edit': false,
};
}
ngOnDestroy(): void {
this._destroying$.next(undefined);
this._destroying$.complete();
}
initializeLD() {
const context = {
kind: 'user',
key: 'my-key',
};
this.ldClient = LDClient.initialize(this.ldClientId, context);
this.ldClient.on('initialized', () => {
const boolFlagValue = this.ldClient.variation(
'allow_edit'
) as boolean;
this.flags['allow_edit'] = boolFlagValue;
this.flagChange.next(this.flags);
});
this.ldClient.on('change', (allChanges) => {
this.flags['allow_edit'] =
allChanges['allow_edit'].current;
this.flagChange.next(this.flags);
});
this.ldClient.on('ready', () => {
const boolFlagValue = this.ldClient.variation(
'allow_edit'
) as boolean;
});
}
}
test.ts
ngMocks.defaultMock(SomeService , () => ({
flags: EMPTY,
flagChange: EMPTY //Have declared it as "flagChange: ()=> EMPTY" as well
}));
component.ts
constructor(private featureFlag: SomeService) {
this.allow_edit= featureFlag.flags['allow_edit'];
this.allow_edit= featureFlag.flagChange.subscribe((flags:any) => {
this.allow_edit= flags['allow_edit'];
});
}
spec.ts
fdescribe('SomeComponent', () => {
let component: SomeComponent;
let fixture: ComponentFixture<SomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HttpClientModule],
declarations: [SomeComponent],
providers: [
MockProvider(SomeService),
],
}).compileComponents();
fixture = TestBed.createComponent(SomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
答案1
得分: 1
以下是翻译好的部分:
问题在于 EMPTY
不满足 Subject<Object>
,因为 EMPTY
没有提供 .next()
。这就是你遇到错误的原因。
要解决这个问题,你有两个选项:强制类型转换(抑制错误)或使用正确的模拟对象(满足类型要求):
强制类型转换
即使 TypeScript 不会显示错误,但在这种情况下,flagChange.next
会失败,因为 EMPTY
没有这个属性。
ngMocks.defaultMock(SomeService, () => ({
flags: EMPTY,
flagChange: EMPTY as never,
}));
正确的模拟对象
https://stackblitz.com/edit/github-topg3g?file=src%2Ftest.spec.ts
ng-mocks
提供 MockService
,它模拟了所有方法,并根据需要应用自定义设置。
ngMocks.defaultMock(SomeService, () => ({
flags: EMPTY,
flagChange: MockService(Subject, EMPTY),
}));
现在 flagChange.next()
是一个模拟函数,.subscribe()
来自于 EMPTY
。
英文:
The thing here is that EMPTY
doesn't satisfy Subject<Object>
, because EMPTY
doesn't provide .next()
. That's why you get the error.
To fix that you have 2 options: cast type (suppress error) or use proper mock (satisfy types):
cast type
even TS won't show you errors, in this case flagChange.next
will fail, because EMPTY
doesn't have it.
ngMocks.defaultMock(SomeService , () => ({
flags: EMPTY,
flagChange: EMPTY as never,
}));
proper mock
https://stackblitz.com/edit/github-topg3g?file=src%2Ftest.spec.ts
ng-mocks
providers MockService
, which mocks all methods and applies customizations if any specified.
ngMocks.defaultMock(SomeService , () => ({
flags: EMPTY,
flagChange: MockService(Subject, EMPTY),
}));
Now flagChange.next()
is a mock function, and .subscribe()
comes from EMPTY
.
答案2
得分: -1
根据这里的第二个示例:https://ng-mocks.sudo.eu/api/MockProvider,尝试:
MockProvider(SomeService, { flags: EMPTY, flagChange: EMPTY }, 'useValue'),
英文:
According to the 2nd example here: https://ng-mocks.sudo.eu/api/MockProvider, try:
MockProvider(SomeService, { flags: EMPTY, flagChange: EMPTY }, 'useValue'),
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论