角度服务规范未达到可观察对象的期望

问题描述 投票:0回答:1

问题

我正在尝试用茉莉花为我拥有的Angular服务编写规范。该服务包装了@azure/msal-angular,还使用Microsoft Graph API来获取登录用户的个人资料图片。我无法成功通过测试。我要尝试做的就是验证当要求图片时Graph API错误(即存在404错误)时,是否捕获了该错误,并返回了我的Assets文件夹中的默认图像。

但是,出现以下错误:enter image description here

下面是我正在接受测试的服务以及测试本身。任何帮助都将不胜感激!

服务

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { BroadcastService, MsalService } from '@azure/msal-angular';
import { Account, AuthError, AuthResponse, ClientAuthError } from 'msal';
import { Observable, ReplaySubject, throwError } from 'rxjs';
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';

import { GraphUser } from '../../models';
import { LocalBlobService } from '../../services';
import { environment } from './../../../environments/environment';

@Injectable({
    providedIn: 'root',
})
export class AuthService {
    isLoggedIn: boolean;

    /** TODO: Cache locally
     * - clear on logout
     * - how often to refresh?
     */
    graphUser$: Observable<GraphUser>;

    /** TODO: Cache locally
     * - clear on logout
     * - how often to refresh?
     */
    graphPicture$: Observable<SafeUrl>;

    get loginFailure$(): Observable<any> {
        return this._loginFailure$.asObservable();
    }

    get loginSuccess$(): Observable<any> {
        return this._loginSuccess$.asObservable();
    }

    get user(): Account {
        const user = this._msal.getAccount();
        this.isLoggedIn = !!user;
        return user;
    }

    private _loginFailure$ = new ReplaySubject<any>();
    private _loginSuccess$ = new ReplaySubject<any>();

    constructor(
        private readonly _http: HttpClient,
        private readonly _msal: MsalService,
        private readonly _broadcasts: BroadcastService,
        private readonly _sanitizer: DomSanitizer,
        private readonly _localBlob: LocalBlobService
    ) {
        this._msal.handleRedirectCallback(this.redirectCallback);
        this.getGraphUserInfo();

        this._broadcasts.subscribe(
            'msal:loginFailure',
            this.loginFailureCallback
        );

        this._broadcasts.subscribe(
            'msal:loginSuccess',
            this.loginSuccessCallback
        );
    }

    updateUserProfilePhoto(file) {
        return this._http
            .put(environment.AD.pictureUrl, file)
            .pipe(
                catchError((error, caught) => this.handlePictureError(error))
            );
    }

    private getGraphUserInfo() {
        if (this.user) {
            this.graphUser$ = this.getGraphUser();
            this.graphPicture$ = this.getGraphPicture();
        }
    }

    private getGraphPicture(): Observable<SafeUrl> {
        return this._http
            .get(environment.AD.pictureUrl, {
                responseType: 'blob',
            })
            .pipe(
                catchError((error, caught) => this.handlePictureError(error)),
                switchMap(blob => this._localBlob.readAsDataURL(blob)),
                map(picture => this._sanitizer.bypassSecurityTrustUrl(picture)),
                shareReplay(1)
            );
    }

    private getGraphUser(): Observable<GraphUser> {
        return this._http.get<GraphUser>(environment.AD.graphUrl).pipe(
            catchError((error, caught) => throwError(error)),
            shareReplay()
        );
    }

    private loginSuccessCallback = payload => {
        this.isLoggedIn = true;
        this._loginSuccess$.next();
        this.getGraphUserInfo();
    };

    private loginFailureCallback = payload => {
        this.isLoggedIn = false;
        this._loginFailure$.next();
    };

    private redirectCallback = (
        redirectError: AuthError,
        redirectResponse: AuthResponse
    ) => {
        if (redirectError) {
            console.error(redirectError);
            return;
        }
        console.log(redirectResponse);
    };

    private handlePictureError(error: ClientAuthError): Observable<Blob> {
        console.log(error);
        return this._http.get('/assets/images/defaultAvatarSmall.png', {
            responseType: 'blob',
        });
    }
}

测试

it('should return default image if graph API picture fails', (done: DoneFn) => {
    // Arrange
    const spy = spyOn(TestBed.inject(HttpClient), 'get')
        .withArgs(environment.AD.graphUrl)
        .and.callThrough()
        .withArgs(environment.AD.pictureUrl, {
            responseType: 'blob',
        } as any)
        .and.returnValue(throwError(new ClientAuthError('')))
        .withArgs('/assets/images/defaultAvatarSmall.png', {
            responseType: 'blob',
        } as any)
        .and.callThrough();

     // Act
    const service: AuthService = TestBed.inject(AuthService);
    service.graphPicture$.subscribe(
        () => {

            // Assert
            expect(spy.calls.allArgs()).toEqual([
                [environment.AD.graphUrl],
                [
                    environment.AD.pictureUrl,
                    { responseType: 'blob' as 'json' },
                ],
                [
                    '/assets/images/defaultAvatarSmall.png',
                    { responseType: 'blob' as 'json' },
                ],
            ]);
            done();
        },
        error => {
            expect(1).toEqual(1);
            done();
        }
    );
});
angular typescript testing jasmine specifications
1个回答
0
投票

我终于明白了。由于这一天,今天真是漫长的一天...

我将getGraphPicture中的AuthService更改为如下所示:


    private getGraphPicture(): Observable<SafeUrl> {
        // Observable stream of image requests where errors are not emitted.
        return onErrorResumeNext<Blob>(
            // Try the logged in user's pictureUrl first
            this._http.get<Blob>(environment.AD.pictureUrl, {
                responseType: 'blob' as 'json',
            }),
            // Try the default avatar picture in assets next
            this._http.get<Blob>('/assets/images/defaultAvatarSmall.png', {
                responseType: 'blob' as 'json',
            })
        ).pipe(
            first(), // Only grab the first successful emission
            switchMap(blob => this._localBlob.readAsDataURL(blob)),
            map(picture => this._sanitizer.bypassSecurityTrustUrl(picture)),
            shareReplay(1)
        );
    }

然后我像这样写了我的测试:


    it('should return default image if graph API picture fails', fakeAsync(() => {
        // Arrange
        const spy = spyOn(TestBed.inject(HttpClient), 'get')
            .withArgs(environment.AD.graphUrl)
            .and.callThrough()
            .withArgs(environment.AD.pictureUrl, {
                responseType: 'blob' as 'json',
            })
            .and.returnValue(throwError(new ClientAuthError('')))
            .withArgs('/assets/images/defaultAvatarSmall.png', {
                responseType: 'blob' as 'json',
            })
            .and.callThrough();

        // Act
        const service: AuthService = TestBed.inject(AuthService);
        tick();

        expect(spy.calls.allArgs()).toEqual([
            [environment.AD.graphUrl],
            [environment.AD.pictureUrl, { responseType: 'blob' as 'json' }],
            [
                '/assets/images/defaultAvatarSmall.png',
                { responseType: 'blob' as 'json' },
            ],
        ]);
    }));

© www.soinside.com 2019 - 2024. All rights reserved.