单元测试 – 使用ng-mocks模拟一个主题

huangapple go评论94阅读模式
英文:

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; flagChange: 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

  1. import { Injectable, OnDestroy } from '@angular/core';
  2. import * as LDClient from 'launchdarkly-js-client-sdk';
  3. import { LDFlagValue } from 'launchdarkly-js-client-sdk';
  4. import { Observable, Subject, of } from 'rxjs';
  5. import { map } from 'rxjs/operators';
  6. @Injectable({
  7. providedIn: 'root',
  8. })
  9. export class SomeService implements OnDestroy {
  10. ldClient!: LDClient.LDClient;
  11. flags: LDClient.LDFlagSet;
  12. flagChange!: Subject<Object>;
  13. private readonly _destroying$ = new Subject<void>();
  14. ldClientId: string;
  15. constructor(private globalvars: GlobalVars) {
  16. this.ldClientId = 'client-id';
  17. this.flags = {
  18. 'allow_edit': false,
  19. };
  20. }
  21. ngOnDestroy(): void {
  22. this._destroying$.next(undefined);
  23. this._destroying$.complete();
  24. }
  25. initializeLD() {
  26. const context = {
  27. kind: 'user',
  28. key: 'my-key',
  29. };
  30. this.ldClient = LDClient.initialize(this.ldClientId, context);
  31. this.ldClient.on('initialized', () => {
  32. const boolFlagValue = this.ldClient.variation(
  33. 'allow_edit'
  34. ) as boolean;
  35. this.flags['allow_edit'] = boolFlagValue;
  36. this.flagChange.next(this.flags);
  37. });
  38. this.ldClient.on('change', (allChanges) => {
  39. this.flags['allow_edit'] =
  40. allChanges['allow_edit'].current;
  41. this.flagChange.next(this.flags);
  42. });
  43. this.ldClient.on('ready', () => {
  44. const boolFlagValue = this.ldClient.variation(
  45. 'allow_edit'
  46. ) as boolean;
  47. });
  48. }
  49. }

test.ts

  1. ngMocks.defaultMock(SomeService , () => ({
  2. flags: EMPTY,
  3. flagChange: EMPTY //Have declared it as "flagChange: ()=> EMPTY" as well
  4. }));

component.ts

  1. constructor(private featureFlag: SomeService) {
  2. this.allow_edit= featureFlag.flags['allow_edit'];
  3. this.allow_edit= featureFlag.flagChange.subscribe((flags:any) => {
  4. this.allow_edit= flags['allow_edit'];
  5. });
  6. }

spec.ts

  1. fdescribe('SomeComponent', () => {
  2. let component: SomeComponent;
  3. let fixture: ComponentFixture<SomeComponent>;
  4. beforeEach(async () => {
  5. await TestBed.configureTestingModule({
  6. imports: [HttpClientModule],
  7. declarations: [SomeComponent],
  8. providers: [
  9. MockProvider(SomeService),
  10. ],
  11. }).compileComponents();
  12. fixture = TestBed.createComponent(SomeComponent);
  13. component = fixture.componentInstance;
  14. fixture.detectChanges();
  15. });
  16. it('should create', () => {
  17. expect(component).toBeTruthy();
  18. });
  19. });
英文:

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

  1. import { Injectable, OnDestroy } from &#39;@angular/core&#39;;
  2. import * as LDClient from &#39;launchdarkly-js-client-sdk&#39;;
  3. import { LDFlagValue } from &#39;launchdarkly-js-client-sdk&#39;;
  4. import { Observable, Subject, of } from &#39;rxjs&#39;;
  5. import { map } from &#39;rxjs/operators&#39;;
  6. @Injectable({
  7. providedIn: &#39;root&#39;,
  8. })
  9. export class SomeService implements OnDestroy {
  10. ldClient!: LDClient.LDClient;
  11. flags: LDClient.LDFlagSet;
  12. flagChange!: Subject&lt;Object&gt;;
  13. private readonly _destroying$ = new Subject&lt;void&gt;();
  14. ldClientId: string;
  15. constructor(private globalvars: GlobalVars) {
  16. this.ldClientId = &#39;client-id&#39;;
  17. this.flags = {
  18. &#39;allow_edit&#39;: false,
  19. };
  20. }
  21. ngOnDestroy(): void {
  22. this._destroying$.next(undefined);
  23. this._destroying$.complete();
  24. }
  25. initializeLD() {
  26. const context = {
  27. kind: &#39;user&#39;,
  28. key: &#39;my-key&#39;,
  29. };
  30. this.ldClient = LDClient.initialize(this.ldClientId, context);
  31. this.ldClient.on(&#39;initialized&#39;, () =&gt; {
  32. const boolFlagValue = this.ldClient.variation(
  33. &#39;allow_edit&#39;
  34. ) as boolean;
  35. this.flags[&#39;allow_edit&#39;] = boolFlagValue;
  36. this.flagChange.next(this.flags);
  37. });
  38. this.ldClient.on(&#39;change&#39;, (allChanges) =&gt; {
  39. this.flags[&#39;allow_edit&#39;] =
  40. allChanges[&#39;allow_edit&#39;].current;
  41. this.flagChange.next(this.flags);
  42. });
  43. this.ldClient.on(&#39;ready&#39;, () =&gt; {
  44. const boolFlagValue = this.ldClient.variation(
  45. &#39;allow_edit&#39;
  46. ) as boolean;
  47. });
  48. }
  49. }

test.ts

  1. ngMocks.defaultMock(SomeService , () =&gt; ({
  2. flags: EMPTY,
  3. flagChange: EMPTY //Have declared it as &quot;flagChange: ()=&gt; EMPTY&quot; as well
  4. }));

component.ts

  1. constructor(private featureFlag: SomeService) {
  2. this.allow_edit= featureFlag.flags[&#39;allow_edit&#39;];
  3. this.allow_edit= featureFlag.flagChange.subscribe((flags:any) =&gt; {
  4. this.allow_edit= flags[&#39;allow_edit&#39;];
  5. });
  6. }

spec.ts

  1. fdescribe(&#39;SomeComponent&#39;, () =&gt; {
  2. let component: SomeComponent;
  3. let fixture: ComponentFixture&lt;SomeComponent&gt;;
  4. beforeEach(async () =&gt; {
  5. await TestBed.configureTestingModule({
  6. imports: [HttpClientModule],
  7. declarations: [SomeComponent],
  8. providers: [
  9. MockProvider(SomeService),
  10. ],
  11. }).compileComponents();
  12. fixture = TestBed.createComponent(SomeComponent);
  13. component = fixture.componentInstance;
  14. fixture.detectChanges();
  15. });
  16. it(&#39;should create&#39;, () =&gt; {
  17. expect(component).toBeTruthy();
  18. });
  19. });

答案1

得分: 1

以下是翻译好的部分:

问题在于 EMPTY 不满足 Subject<Object>,因为 EMPTY 没有提供 .next()。这就是你遇到错误的原因。

要解决这个问题,你有两个选项:强制类型转换(抑制错误)或使用正确的模拟对象(满足类型要求):

强制类型转换

即使 TypeScript 不会显示错误,但在这种情况下,flagChange.next 会失败,因为 EMPTY 没有这个属性。

  1. ngMocks.defaultMock(SomeService, () => ({
  2. flags: EMPTY,
  3. flagChange: EMPTY as never,
  4. }));

正确的模拟对象

https://stackblitz.com/edit/github-topg3g?file=src%2Ftest.spec.ts

ng-mocks 提供 MockService,它模拟了所有方法,并根据需要应用自定义设置。

  1. ngMocks.defaultMock(SomeService, () => ({
  2. flags: EMPTY,
  3. flagChange: MockService(Subject, EMPTY),
  4. }));

现在 flagChange.next() 是一个模拟函数,.subscribe() 来自于 EMPTY

英文:

The thing here is that EMPTY doesn't satisfy Subject&lt;Object&gt;, 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.

  1. ngMocks.defaultMock(SomeService , () =&gt; ({
  2. flags: EMPTY,
  3. flagChange: EMPTY as never,
  4. }));

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.

  1. ngMocks.defaultMock(SomeService , () =&gt; ({
  2. flags: EMPTY,
  3. flagChange: MockService(Subject, EMPTY),
  4. }));

Now flagChange.next() is a mock function, and .subscribe() comes from EMPTY.

答案2

得分: -1

根据这里的第二个示例:https://ng-mocks.sudo.eu/api/MockProvider,尝试:

  1. MockProvider(SomeService, { flags: EMPTY, flagChange: EMPTY }, 'useValue'),
英文:

According to the 2nd example here: https://ng-mocks.sudo.eu/api/MockProvider, try:

  1. MockProvider(SomeService, { flags: EMPTY, flagChange: EMPTY }, &#39;useValue&#39;),

huangapple
  • 本文由 发表于 2023年6月9日 04:02:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76435337.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定