Angular 为什么在运行测试时会出现“失败:无法读取未定义的属性‘subscribe’”?

Angular 为什么在运行测试时会出现“失败:无法读取未定义的属性‘subscribe’”?,angular,observable,karma-jasmine,Angular,Observable,Karma Jasmine,我对Angular2+的测试比较陌生,正在为测试设置文件。自从建立这个项目以来,我已经写了很多代码,现在正在尝试测试其中的部分。然而,这些测试中的很多现在都被破坏了,所以我现在尝试删除不需要的默认测试或者修复那些有用的测试 此特定测试不会运行,因为它在到达it测试方法之前失败。我不确定我错过了什么,任何帮助都将不胜感激 我收到这个错误: Failed: Cannot read property 'subscribe' of undefined TypeError: Cannot read pro

我对Angular2+的测试比较陌生,正在为测试设置文件。自从建立这个项目以来,我已经写了很多代码,现在正在尝试测试其中的部分。然而,这些测试中的很多现在都被破坏了,所以我现在尝试删除不需要的默认测试或者修复那些有用的测试

此特定测试不会运行,因为它在到达it测试方法之前失败。我不确定我错过了什么,任何帮助都将不胜感激

我收到这个错误:

Failed: Cannot read property 'subscribe' of undefined
TypeError: Cannot read property 'subscribe' of undefined
    at AppComponent../src/app/app.component.ts.AppComponent.setupLanguageTranslation (src/app/app.component.ts:48:26)
    at AppComponent../src/app/app.component.ts.AppComponent.ngOnInit (src/app/app.component.ts:32:14)
    at checkAndUpdateDirectiveInline (node_modules/@angular/core/fesm5/core.js:22089:1)
    at checkAndUpdateNodeInline (node_modules/@angular/core/fesm5/core.js:23353:1)
    at checkAndUpdateNode (node_modules/@angular/core/fesm5/core.js:23315:1)
    at debugCheckAndUpdateNode (node_modules/@angular/core/fesm5/core.js:23949:1)
    at debugCheckDirectivesFn (node_modules/@angular/core/fesm5/core.js:23909:1)
    at Object.eval [as updateDirectives] (ng:///DynamicTestModule/AppComponent_Host.ngfactory.js:9:9)
    at Object.debugUpdateDirectives [as updateDirectives] (node_modules/@angular/core/fesm5/core.js:23901:1)
    at checkAndUpdateView (node_modules/@angular/core/fesm5/core.js:23297:1)
我已经尝试了很多类似问题的文章,包括但不限于:

App.component.ts

export class AppComponent implements OnInit, OnDestroy {
    title = 'planox';
    showError = false;
    errorMessage = '';
    translatedText: string;

    constructor(private _translate: TranslateService,
                private utils: Utils) {}

    ngOnInit(): void {
        this.setupLanguageTranslation();
    }

    ngOnDestroy(): void {
        this._translate.onLangChanged.unsubscribe();
    }

    setupLanguageTranslation() {
        this.subscribeToLangChanged();
        // set language
        this._translate.setDefaultLang('en'); // set English as default
        this._translate.enableFallback(true); // enable fallback

        // set current language
        this.selectLang('en');

        this.utils.error.subscribe(response => {
            this.errorMessage = response;
            this.showError = true;
            setTimeout( () => {
                this.showError = false;
            }, 2000);
        });
    }

    selectLang(lang: string) {
        // set default;
        this._translate.use(lang);
    }

    subscribeToLangChanged() {
        // refresh text
        this.refreshText();
        return this._translate.onLangChanged.value;
    }

    refreshText() {
        // refresh translation when language change
        this.translatedText = this._translate.instant('hello world');
    }

}
app.component.spec.ts

import {TestBed, async, ComponentFixture, inject} from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import {TranslateService} from './translate/translate.service';
import Utils from './shared/utils';
import {MockTranslateService} from './mocks/mockTranslateService';
import {MockUtils} from './mocks/mockUtils';
import {TRANSLATIONS} from './translate/translation';


describe('AppComponent', () => {
    let fixture: ComponentFixture<AppComponent>;

    beforeEach(async(() => {
        const mockTranslate = new MockTranslateService(TRANSLATIONS);
        TestBed.configureTestingModule({
            imports: [
                RouterTestingModule
            ],
            declarations: [
                AppComponent
            ],
            providers: [
                {provide: TranslateService, useValue: mockTranslate},
                {provide: Utils, useValue: MockUtils},
            ]
        });

        fixture = TestBed.createComponent(AppComponent);
        fixture.detectChanges();
    }));

    it('should create the app', () => {
        fixture = TestBed.createComponent(AppComponent);
        const app = fixture.debugElement.componentInstance;
        expect(app).toBeTruthy();
    });
});
Utils.ts:

import {Subject, throwError} from 'rxjs';
import {Injectable} from '@angular/core';
import {Router} from '@angular/router';
import {Role} from '../classes/role/role.enum';

export enum KEY_CODE {
    ENTER = 13,
    RIGHT_ARROW = 39,
    LEFT_ARROW = 37
}

@Injectable({
    providedIn: 'root'
})
export default class Utils {
    error: Subject<string> = new Subject<string>();

    constructor(private router: Router) {}

    handleError(service: string, error: Response | any, customMessage?: string) {
        const err = service + '::handleError' + '=  ' + error;
        this.error.next(customMessage);
        return throwError(error);
    }

    encodeBase64(value: any) {
        return btoa(JSON.stringify(value));
    }

    decodeBase64(value: any) {
        return atob(value);
    }

    navigateToDefaultPage(usersRole: Role) {
        if (usersRole === Role.Employee) {
            this.router.navigate(['/dashboardNavigation', {outlets: {dashNav: ['productionDashboard']}}]);
        } else if (usersRole === Role.Client) {
            this.router.navigate(['/clientDashboard']);
        } else if (usersRole === Role.OfficeAdmin) {
            this.router.navigate(['/dashboardNavigation', {outlets: {dashNav: ['enterpriseDashboard']}}]);
        } else {
            this.router.navigate(['/auth']);
        }
    }

    validateEmail(inputText: string) {
        const mailFormat = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
        if (inputText.match(mailFormat)) {
            return true;
        } else {
            return false;
        }
    }
}
mockUtils.ts

import Utils from "../shared/utils";
import {Subject} from "rxjs";

export class MockUtils extends Utils {
    error = new Subject<string>();
}
基本上,我需要帮助设置测试页面的结构,以便开始编写有用的测试

我可以提供您可能需要帮助的任何其他代码,但我不确定这需要什么。

删除fixture.detectChanges from beforeach方法,从那里删除它,并将其移动到每个测试中(如果需要),将为您修复它,我认为这是一个bug,在Angular 6和Angular 7上看到过

因此,您的beforeach将更改为:

beforeEach(async(() => {
    const mockTranslate = new MockTranslateService(TRANSLATIONS);
    TestBed.configureTestingModule({
        imports: [
            RouterTestingModule
        ],
        declarations: [
            AppComponent
        ],
        providers: [
            {provide: TranslateService, useValue: mockTranslate},
            {provide: Utils, useValue: MockUtils},
        ]
    });

    fixture = TestBed.createComponent(AppComponent);
    // detection deleted here
}));
从beforeach方法中删除fixture.detectChanges,从那里删除它,如果需要,将其移动到每个测试中,将为您修复它,我认为这是一个bug,在Angular 6和Angular 7上看到过

因此,您的beforeach将更改为:

beforeEach(async(() => {
    const mockTranslate = new MockTranslateService(TRANSLATIONS);
    TestBed.configureTestingModule({
        imports: [
            RouterTestingModule
        ],
        declarations: [
            AppComponent
        ],
        providers: [
            {provide: TranslateService, useValue: mockTranslate},
            {provide: Utils, useValue: MockUtils},
        ]
    });

    fixture = TestBed.createComponent(AppComponent);
    // detection deleted here
}));

你能添加MockUtils定义吗?@LostJon它现在只是一个包装器,里面什么都没有,这是我在中看到的一个模式。我是否应该尝试覆盖其中失败的变量?这就是你的问题……Utils初始化时出现错误:Subject=newsubject;因此,您的模拟错误需要执行以下操作:same@LostJon所以我刚刚加了这个,我仍然得到同样的错误。也添加了上面的文件。现在查看MockUtils,您不需要在其中添加该错误,因为它扩展了Utils…我唯一能提供的其他调试是在执行subscribe调用之前对console.LOGTHE和console.LOGTHE.Utils进行调试,以确保它们按照您想要的方式填充。您可以添加MockUtils定义。@LostJon它现在只是一个包装器,没有任何内容,这是我看到的一种模式。我是否应该尝试覆盖其中失败的变量?这就是你的问题……Utils初始化时出现错误:Subject=newsubject;因此,您的模拟错误需要执行以下操作:same@LostJon所以我刚刚加了这个,我仍然得到同样的错误。也添加了上面的文件。现在查看MockUtils,您不需要在其中添加该错误,因为它扩展了Utils…我可以提供的唯一其他调试是在执行subscribe调用之前对console.logthis和console.logthis.Utils进行调试,以确保它们的填充方式与您在同一错误中遇到的方式相同,令人惊讶的是,这解决了这个问题。谢谢我的项目使用Angular 8.3,这对我来说仍然有效。谢谢你救了我的命。我仍然觉得这更像是一个解决方法,而不是一个实际的修复方法。我正在与同样的错误作斗争,令人惊讶的是,这解决了它。谢谢我的项目使用Angular 8.3,这对我来说仍然有效。谢谢你救了我的命。仍然感觉更像是一种解决方法,而不是实际的修复方法。