更改模拟服务在Angular / Jasmine / Redux中每个测试返回的可观察值

时间:2019-01-14 05:01:29

标签: angular unit-testing jasmine karma-jasmine ngrx

我有一个返回ngrx选择器公开的值的服务。 组件定义此服务以获取数据。 我正在使用服务的模拟为组件编写单元测试,并且我需要模拟服务为每个单元测试返回不同的值。 我该如何实现?

组件

@Component({
    selector: 'app-test',
    templateUrl: './test.component.html',
    providers: [TestService],

})
    export class TestComponent {

        test$ = this.testService.test$;
        test: number;

        constructor(private service: TestService) {
            service.test$.subscribe(test => this.test = test);
        }
    }

服务

export class TestService {

        test$ = this.store.select(getTestValueFromStore);

        constructor(private store: Store<any>){}
}

尝试1(重置服务的值):不起作用

class MockTestService {
    **test$ = of(10);**
}

describe('TestComponent', () => {
    let testService: TestService;

    beforeEach((() => {
        // Define component service
        TestBed.overrideComponent(
            TestComponent,
            { set: { providers: [{ provide: TestService, useClass: MockTestService }] } }
        );

        TestBed.configureTestingModule({
            declarations: [TestComponent]
        })
            .compileComponents();
    }));

    beforeEach(async() => {
        fixture = TestBed.createComponent(TestComponent);
        component = fixture.componentInstance;
        testService = fixture.debugElement.injector.get(TestService);
        fixture.detectChanges();
    });

it('should do something when the value returned by the service is 20', fakeAsync(() => {

        **testService.test$ = of(20);**

        tick();

        expect(component.test).toEqual(20);
    }));
});

尝试2:使用主题。业力引发错误 “类型为“可观察”的属性“下一个”不存在” 因为TestService返回的是可观察值,而不是主题

class MockTestService {
        **test$ = new BehaviourSubject(10);**
    }

    describe('TestComponent', () => {
        let testService: TestService;

        beforeEach((() => {
            // Define component service
            TestBed.overrideComponent(
                TestComponent,
                { set: { providers: [{ provide: TestService, useClass: MockTestService }] } }
            );

            TestBed.configureTestingModule({
                declarations: [TestComponent]
            })
                .compileComponents();
        }));

        beforeEach(async() => {
            fixture = TestBed.createComponent(TestComponent);
            component = fixture.componentInstance;
            testService = fixture.debugElement.injector.get(TestService);
            fixture.detectChanges();
        });

    it('should do something when the value returned by the service is 20', fakeAsync(() => {

            **testService.test$.next(20);**

            tick();

            expect(component.test).toEqual(20);
        }));
    });

3 个答案:

答案 0 :(得分:2)

您在问题中说,您希望“模拟服务为每个单元测试返回不同的值”。为此,您将必须对定义组件的方式进行一些更改。

  1. 最重要的更改是将预订移到ngOnInit()中,并移出了组件的构造函数。如果这样做,则可以控制何时触发订阅,否则将在创建组件时触发订阅,从而很难为每个单元测试返回不同的值。

  2. 一旦完成,那么您在致电fixture.detectChanges()时要小心。这是因为它将执行ngOnInit(),这将执行预订并将将从Observable返回的值存储到component.test中。请注意,由于您使用的是of(),它只会发出一次然后完成。如果您将新值放入testService.test$,它将不会再次发出。

  3. 设置完成后,您可以在调用Fixture.detectChanges之前更改testService.test$的值,并根据需要更改值。

我已经设置了一个StackBlitz来演示这一点,并尽可能少地更改代码以使其正常工作。有两个测试,每个测试都返回不同的值。

我希望这会有所帮助!

答案 1 :(得分:1)

尝试这样的事情

describe('TestComponent', () => {
    beforeEach((() => {
        TestBed.configureTestingModule({
            declarations: [TestComponent],
            providers: [
              { provide: TestService, useValue: { test$: of(20) } }
            ]
        })
            .compileComponents();
    }));

    beforeEach(async() => {
        fixture = TestBed.createComponent(TestComponent);
        component = fixture.componentInstance;
        testService = fixture.debugElement.injector.get(TestService);
        fixture.detectChanges();
    });

   it('should do something when the value returned by the service is 20', () => {
        expect(component.test).toEqual(20);
    });
});

您无需在此处使用fakeAsync,因为of()是同步发出的。

此外,请考虑在ngOnInit挂钩中订阅可观察的内容。构造函数应仅用于依赖项注入。

答案 2 :(得分:0)

如果您尝试测试非组件 Angular 项目(即具有服务依赖项的 AuthGuard),您将无法使用 fixture.detectChanges() 技巧,因为您将没有组件来检测更改.

在这种情况下,您应该使用间谍:

describe('AuthGuardTest', () => {
    let testBed: TestBed;
    let authGuard: AuthGuard;
    let testService: TestService;

    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [HttpClientTestingModule],
        providers: [AuthGuard]
      });

      testBed = getTestBed();
      testService = testBed.inject(TestService);
      authGuard = testBed.inject(AuthGuard);
    });

    it('should return 1000', () => {
      // Mock the method in your testService to return 1000
      spyOn(testService, 'getNumber').and.returnValue(1000);

      // Call code that references testService.getNumber() and Assert 1000 was returned
    });

    it('should return 2000', () => {
      // Mock the method in your testService to return 2000
      spyOn(testService, 'getNumber').and.returnValue(2000);

      // Call code that references testService.getNumber() and Assert 2000 was returned
    });
});

有关更多示例,请查看这些教程:

模拟和间谍 - https://codecraft.tv/courses/angular/unit-testing/mocks-and-spies/

单元测试 AuthGuard - https://keepgrowing.in/angular/how-to-test-angular-authguard-examples-for-the-canactivate-interface/