【问题标题】:How to unit test Observable.zip(observable1, observable2)如何单元测试 Observable.zip(observable 1, observable 2)
【发布时间】:2017-11-02 16:21:09
【问题描述】:

我正在尝试对使用 Observable.zip() 的组件进行单元测试

服务代码

如您所见,BehaviorSubject 是用 null 初始化的,但在测试代码的 beforeAll 中,我强制他返回一个 >Observable.of

export class ProfiloUtenteService extends BaseService<ProfiloDto> {
    public static readonly profiloKey = 'profiloUtente';

    private _$profilo = new BehaviorSubject<ProfiloDto>(null);
    public $profilo = this._$profilo.asObservable();

    protected get storedProfilo(): ProfiloDto {
        const profilo = this.storageService.retrieve(ProfiloUtenteService.profiloKey);
        return profilo ? profilo : null;
    }
    protected set storedProfilo(profilo: ProfiloDto) {
        this.storageService.store(ProfiloUtenteService.profiloKey, profilo);
        this._$profilo.next(profilo);
    }

    private emptyProfile = {
        addettoId: null,
        scope: Scope.none,
        selectedScope: Scope.none,
        ufficioId: null,
        provinciaUfficioId: null,
        addettoCognome: null,
        addettoNome: null
    };

    constructor(
        protected httpClient: HttpClient,
        protected appConfig: AppConfig,
        protected storageService: StorageService
    ) {
        super(httpClient, appConfig, appConfig.endpoints.addetto.api.baseUrl);

        const profilo = this.storedProfilo;
        if (profilo) {
            this._$profilo.next(profilo);
        }
    }

    public updateProfiloUtente(): void {
        const readProfilo = this.storedProfilo;
        if (readProfilo) {
            this._$profilo.next(readProfilo);
        }else {
            super.get((<AppConfig>this.appConfig).endpoints.addetto.api.routes.profilo)
                .takeLast(1)
                .do(profilo => {
                    if (!profilo) {
                        this._$profilo.next(this.emptyProfile);
                    }
                })
                .filter(profilo => !!profilo)
                .subscribe(profilo => {
                    profilo.selectedScope = Scope.all;
                    this.storedProfilo = profilo;
                });
        }
    }

    public setSelectedScope(scope: Scope) {
        const profilo = this.storedProfilo;
        if (profilo) {
            profilo.selectedScope = scope;
            this.storedProfilo = profilo;
        }
    }

    public setSelectedUfficioId(ufficioId: number, provinciaUfficioId?: number) {
        const profilo = this.storedProfilo;
        if (profilo) {
            profilo.ufficioId = ufficioId;
            profilo.provinciaUfficioId = provinciaUfficioId || null;
            this.storedProfilo = profilo;
        }
    }

    public logout() {
        this.storedProfilo = null;
    }
}

组件代码

这里爆炸了。当它订阅 Observable.zip 并尝试从中获取结果 [1] 时,此结果为 NULL。

Observable.zip(
    this.ufficioService.getODataForCombo({ skip: 0 }),
    this.profiloUtenteService.$profilo)
    .takeWhile(() => this.isAlive)
    .subscribe(result => {
        result[0].forEach(office => this.availableOffices.push(office));

        // when this point is reached an error is thrown
        this.selectedOfficeId = result[1].ufficioId;             
        this.selectedOfficeDescription = this.availableOffices.find(office => office.id === this.selectedOfficeId).descrizione;
});

无法读取 null 的属性“ufficioId”

看起来压缩后的 observable 的结果 [1] 没有返回值。我也尝试用 Observable.combineLatest 切换 Observable.zip,但没有任何结果。错误是一样的。

测试代码:

beforeAll(() => {
        ufficioServiceMock = new UfficioService(null, fixedAppConfig);
        spyOn(ufficioServiceMock, 'getODataForCombo').and.returnValue(Observable.of([]));

        profiloUtenteServiceMock = new ProfiloUtenteService(null, fixedAppConfig, null);
        profiloUtenteServiceMock.$profilo = Observable.of({
            addettoId: 1,
            ufficioId: 1,
            provinciaUfficioId: 1,
            scope: 1,

            addettoNome: 'string',
            addettoCognome: 'string',

            selectedScope:  1
        });
    });

新的测试平台代码版本(将服务 $profilo 更改为 getter)

即使将属性 $profilo 转换为 getter 并监视它返回一个值,也没有任何变化......

describe('PraticheSearchComponent', () => {
    let comp: PraticheSearchComponent;
    let fixture: ComponentFixture<PraticheSearchComponent>;
    let de: DebugElement;
    let el: HTMLElement;

    let ufficioServiceMock: UfficioService;
    let profiloUtenteServiceMock: ProfiloUtenteService;

    beforeAll(() => {
        ufficioServiceMock = new UfficioService(null, fixedAppConfig);
        spyOn(ufficioServiceMock, 'getODataForCombo').and.returnValue(Observable.of([]));

        profiloUtenteServiceMock = new ProfiloUtenteService(null, fixedAppConfig, null);
        spyOn(profiloUtenteServiceMock, '$profilo').and.returnValue(new BehaviorSubject({
            addettoId: 1,
            ufficioId: 1,
            provinciaUfficioId: 1,
            scope: 1,

            addettoNome: 'string',
            addettoCognome: 'string',

            selectedScope: 1
        }).asObservable());
    });
    //     profiloUtenteServiceMock.$profilo = new BehaviorSubject({
    //         addettoId: 1,
    //         ufficioId: 1,
    //         provinciaUfficioId: 1,
    //         scope: 1,

    //         addettoNome: 'string',
    //         addettoCognome: 'string',

    //         selectedScope: 1
    //     }).asObservable();
    // });

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [
                PraticheSearchComponent
            ],
            imports: [
                NgProgressModule,
                AuthModule.forRoot(),
                CustomHttpHeadersModule.forRoot(),

                SharedModule.forRoot(),

                ReactiveFormsModule,
                PatronatoSharedModule,
                FrameworkCoreModule.forRoot(),
                LoggerModule.forRoot(Level.LOG),
                MaterialModule,
                BrowserAnimationsModule,
                RouterTestingModule.withRoutes([])
            ],
            providers: [
                { provide: AppConfig, useValue: fixedAppConfig },
                { provide: LocalizationKeys, useValue: new LocalizationKeys() },
                { provide: ProfiloUtenteService, useValue: profiloUtenteServiceMock },
                { provide: NavbarService, useValue: new NavbarServiceMock() },
                { provide: PraticheSearchService, useValue: new PraticheSearchServiceMock() },
                { provide: UfficioService, useValue: ufficioServiceMock }
            ]
        }).compileComponents();

        fixture = TestBed.createComponent(PraticheSearchComponent);
        comp = fixture.componentInstance;
        de = fixture.debugElement;
        el = de.nativeElement;
    }));

    it('should create', () => {
        expect(comp).toBeTruthy();
    });
});

我错过了什么吗?我从昨天早上开始就遇到了这个问题,我即将把我的工作站撞到地板上。任何帮助都非常感谢(机器:P)

【问题讨论】:

  • 你能做一个jsbin的demo吗?你可以使用这个模板jsbin.com/vocozuy/edit?js,console
  • .takeWhile(() =&gt; this.isAlive) - 恭喜您在销毁时使用了更明智的退订方式!
  • @martin 在 jsbin 上重现这个会有点复杂。这是一个 Angular 5 项目,包含 Jasmine、Typescript 和许多其他 npm 包。对于“简单”演示,我不知道从哪里开始使用 jsbin。我已经编辑了我的问题,因此您也可以看到原始服务。

标签: angular unit-testing jasmine rxjs


【解决方案1】:

撇开单元测试不谈,在我看来 zip() 不可能在 result[1] 为空的情况下发出结果,除非 this.profiloUtenteService.$profilo 发出空值。

快速测试,试试

this.profiloUtenteService.$profilo
  .filter(x => x)

为服务模拟编辑

当我使用 .and.returnValue 时,我使用 jasmine 创建模拟,

const mockService  = jasmine.createSpyObj('ProfiloUtenteService ', ['$profilo']);
mockService.$profilo.and.returnValue(...)

您的代码也可能没问题,但上面的代码对我有用。
请注意,有时不会创建依赖项,但不会引发错误。也许服务的基类导致失败,我在提供者列表中看不到它。使用与茉莉花完全分离的模拟将消除这种情况。

【讨论】:

  • 我可以确保 this.profiloUtenteService.$profilo 不会发出空值,因为我强制他返回一个对象。检查这个工作。但由于某种原因,在运行测试时, res[1] 为空。 combineLatest 的行为也相同。顺便说一句,感谢您的 (this.isAlive) 评论 :)
  • 我可以建议的下一件事是,您在组件的构造中没有使用 mock(因此 new BehaviorSubject&lt;ProfiloDto&gt;(null) 仍在使用中)。能否请您发布测试床代码?
  • 当然,我也是这么想的。但是检查源并在服务中设置一些断点,我看到使用了真正的服务,但是使用了我传递给它的模拟参数。您可以在 beforeAll 中看到我新建了一个传递“假”参数的类。我已经用所有测试平台代码编辑了这个问题。此外,我正在尝试将该属性转换为 getter 和 spyOn,希望它能修复...
【解决方案2】:

我笨

感谢所有试图解决这个问题的人。

解决办法:

我正在处理错误的 spec 文件。从 app.component.spec.ts 开始但从 pratache.component.ts 引发错误的测试strong> 所以我错误地认为错误在 pratache.component.spec.tsapp.component.spec.ts 需要模拟并将代码从一个文件粘贴到另一个固定的一切

【讨论】:

    猜你喜欢
    • 2021-12-24
    • 2017-03-22
    • 2016-04-09
    • 2020-08-30
    • 2017-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-23
    相关资源
    最近更新 更多