Angular/Jest - 测试对象在调用 setter 后是否抛出错误

Angular/Jest - Test that the subject has thrown error after setter is called

我有一个 class 负责为我的应用程序设置主题。

我正在尝试测试如果你给它一个它无法识别的主题它会抛出一个错误。代码如下-

@Injectable()
export class ThemeSwitcherService {
    public static THEME_NAME_COOKIE = 'THEME_NAME';

    private readonly _themeNameBehaviourSubject$: BehaviorSubject<string>;
    private readonly _document: Document;
    private readonly _themes: Map<string, Map<string, string>>;

    constructor(@Inject(DOCUMENT) document: Document, themeConfig: ApplicationThemeConfig) {
        this._themeNameBehaviourSubject$ = new BehaviorSubject<string>('');
        this._document = document;
        this._themes = themeConfig.moduleThemes;

        this.initialise();
    }

    public get currentThemeNameSubscription(): Observable<string> {
        return this._themeNameBehaviourSubject$.asObservable();
    }

    public set themeName(themeName: string) {
        this._themeNameBehaviourSubject$.next(themeName);
    }

    private setTheme(themeName: string): void {
        const selectedTheme: Map<string, string> | undefined = this._themes.get(themeName);

        if (typeof selectedTheme === 'undefined') {
            throw new Error(`Could not find theme named ${ themeName }. Please add ${ themeName } to your forRoot configuration`);
        }

        selectedTheme.forEach((value: string, key: string) => {
            const root = this._document.body;
            root.style.setProperty(key, value);
        });
    }

    private initialise(): void {
        const storedTheme = Cookies.get(ThemeSwitcherService.THEME_NAME_COOKIE);

        if (storedTheme) {
            this.themeName = storedTheme;
        }

        if (!storedTheme) {
            const firstThemeAvailable = this._themes.entries().next().value;
            this.themeName = firstThemeAvailable[0];
        }

        this.subscribeToThemeNameSubject();
    }

    private subscribeToThemeNameSubject(): void {
        this._themeNameBehaviourSubject$
            .pipe(tap((themeName: string) => Cookies.set(ThemeSwitcherService.THEME_NAME_COOKIE, themeName)))
            .subscribe({
                next: (themeName: string) => this.setTheme(themeName),
                error: (error) => console.error(error)
            });
    }
}

当使用值调用 themeName setter 时,它会更新行为主题并调用私有 setTheme(themeName: string) 方法,如果主题未知,则会抛出错误。

我正在尝试测试这行代码:

if (typeof selectedTheme === 'undefined') {
            throw new Error(`Could not find theme named ${ themeName }. Please add ${ themeName } to your forRoot configuration`);
        }

到目前为止我有这个:


import { ThemeSwitcherService } from './theme-switcher.service';
import { ApplicationThemeConfig } from '../setup/application-theme.config';
import { TestBed } from '@angular/core/testing';
import { ApplicationThemeConfigurationFactory } from '../../../config/theme/factories/application-theme-configuration.factory';
import { ThemeModule } from '../theme.module';
import DoneCallback = jest.DoneCallback;

describe('ThemeSwitcherService', () => {
    let subject: ThemeSwitcherService;

    describe('Given a theme that does not exist', () => {

        beforeEach(() => {
            TestBed.configureTestingModule({
                imports: [ThemeModule],
                providers: [
                    {
                        provide: ApplicationThemeConfig,
                        useValue: {
                            moduleThemes: ApplicationThemeConfigurationFactory.getThemes()
                        }
                    }
                ]
            });

            subject = TestBed.inject<ThemeSwitcherService>(ThemeSwitcherService);
        });

        describe('When the theme is selected', () => {
            it('Then an error is thrown', (done: DoneCallback) => {
                subject.themeName = 'Something';
                subject.currentThemeNameSubscription
                    .subscribe((currentTheme) => {
                        expect(currentTheme).toThrowError();
                        done();
                    });

            });
        });
    });
});

但我一直收到错误消息:

Error: Could not find theme named Something. Please add Something to your forRoot configuration

这是我所期望的,但我也希望我的单元测试能够通过。我不确定我做错了什么。

throw new Error 不是观察流的一部分,因此它不会被订阅 currentThemeNameSubscription

捕获

最快的选择是结束对 this.setTheme(themeName) 在常规 try/catch 块中调用错误处理方法(监视测试中的错误处理方法)

我不确定 BehaviorSubject 在这种情况下 tbh

的目的

正如@Drenai 所说,不需要 BehaviourSubject。我已经删除了行为主题,服务变得干净了,应该很容易测试。

重构:

@Injectable()
export class ThemeSwitcherService {
    public static THEME_NAME_COOKIE = 'THEME_NAME';

    private _currentThemeName: string;
    private readonly _document: Document;
    private readonly _themes: Map<string, Map<string, string>>;

    constructor(@Inject(DOCUMENT) document: Document, themeConfig: ApplicationThemeConfig) {
        this._document = document;
        this._themes = themeConfig.moduleThemes;

        this.initialise();
    }

    public get currentThemeName(): string {
        return this._currentThemeName;
    }

    public setTheme(themeName: string): void {
        const selectedTheme: Map<string, string> | undefined = this._themes.get(themeName);

        if (typeof selectedTheme === 'undefined') {
            throw new Error(`Could not find theme named ${ themeName }. Please add ${ themeName } to your forRoot configuration`);
        }

        selectedTheme.forEach((value: string, key: string) => {
            const root = this._document.body;
            root.style.setProperty(key, value);
        });

        this._currentThemeName = themeName;
        Cookies.set(ThemeSwitcherService.THEME_NAME_COOKIE, themeName);
    }

    private initialise(): void {
        const storedTheme = Cookies.get(ThemeSwitcherService.THEME_NAME_COOKIE);

        if (storedTheme) {
            this._currentThemeName = storedTheme;
        }

        if (!storedTheme) {
            const firstThemeAvailable = this._themes.entries().next().value;
            this._currentThemeName = firstThemeAvailable[0];
        }

        this.setTheme(this._currentThemeName);
    }
}