【问题标题】:Angular Jest or Jasmine Testing: How to Properly Spy/Mock a Static Object Called From Within a Tested Class?Angular Jest 或 Jasmine 测试:如何正确监视/模拟从测试类中调用的静态对象?
【发布时间】:2020-02-22 09:15:21
【问题描述】:

我有一个 AppConfigService,它将 JSON 文件中的对象加载到作为服务一部分的静态设置变量中。整个应用程序中的各种组件和/或服务使用 AppConfigService.settings. 引用对象,使用简单引用(无注入)。如何测试引用这种结构的服务?

例如

@Injectable()
export class SomeService {
someVariable;
  constructor() {
    // I can't get the test to not give me a TypeError: Cannot read property 'someSettingsVariable' of undefined on this line
    this.someVariable = AppConfigService.settings.someSettingsVariable;
  }
}

我有两个项目,一个使用 Jest,另一个使用 Jasmine/Karma,我需要弄清楚如何让测试在这个构造中工作的模式。

我尝试过类似的方法:

const spy = spyOnProperty(SomeService, 'someVariable')
        .and.returnValue('someValue');

示例规范:

import { TestBed } from '@angular/core/testing';
import { NgRedux } from '@angular-redux/store';
import { Injectable } from '@angular/core';
import { DispatchHelper } from '../reducers/dispatch.helper';
import { ContributorActions } from '../actions/contributor.action';
import { MockDispatchHelper } from '../_mocks/DispatchHelperMock';
import { DiscrepancyService } from '../discrepancies/discrepancy.service';
import { DiscrepancyAPIService } from '../discrepancies/discrepancy-api.service';
import { DiscrepancyAPIServiceMock } from '../_mocks/DiscrepancyAPIServiceMock';
import { Observable } from 'rxjs';
import { Guid } from 'guid-typescript';
import { getInitialUserAccountState } from '../functions/initial-states/user-account-initial-state.function';
import { LoggingService } from '../security/logging/logging.service';
import { MockLoggingService } from '../_mocks/LoggingServiceMock';

describe('discrepancyService', () => {

    let discrepancyService: DiscrepancyService;

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
                { provide: Injectable, useClass: Injectable },
                { provide: DispatchHelper, useClass: MockDispatchHelper },
                { provide: ContributorActions, useClass: ContributorActions },
                { provide: NgRedux, useClass: NgRedux },
                { provide: DiscrepancyService, useClass: DiscrepancyService },
                { provide: DiscrepancyAPIService, useClass: DiscrepancyAPIServiceMock },
                { provide: LoggingService, useClass: MockLoggingService },
            ]
        })
            .compileComponents();

        const userStateObservable = Observable.create(observer => {
            const userState = getInitialUserAccountState();
            userState.userId = Guid.parse('<guid>');
            userState.organization_id = Guid.parse('<guid>');
            observer.next(userState);
            console.log('built user state observable');
            observer.complete();
        });

        discrepancyService = TestBed.get(DiscrepancyService);
        const spy4 = spyOnProperty(discrepancyService, 'userState$', 'get').and.returnValue(userStateObservable);
    });


    // TODO: Fix this
    it('should create service and loadDiscrepancies', () => {
      // in this example, discrepancyService constructor sets the
      // value of a variable = ApiConfigService.settings.endPoint
      // ApiConfigService.settings is static; how do I "replace"
      // the value of endPoint in a call like this so I don't get
      // an error because ApiConfigService.settings is undefined
      // when called from a service in the test?
      const spy = spyOn(discrepancyService.dispatcher, 'dispatchPayload');
      discrepancyService.loadDiscrepancies();
      expect(spy.calls.count()).toEqual(1);
    });

});

karma.conf.js

// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma'),
      require('karma-spec-reporter')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, '../coverage'),
      reports: ['html', 'lcovonly'],
      fixWebpackSourcePaths: true
    },
    customLaunchers: {
      ChromeDebug: {
        base: 'Chrome',
        flags: [ '--remote-debugging-port=9333','--disable-web-security' ]
      },
      ChromeHeadlessCI: {
        base: 'Chrome',
        flags: ['--no-sandbox', '--headless', '--watch=false'],
        browserDisconnectTolerance: 10,
        browserNoActivityTimeout: 10000,
        browserDisconnectTimeout: 5000,
        singleRun: false
      }
    },
    reporters: ['progress', 'kjhtml', 'spec'],
    port: 9876,
    host: 'localhost',
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['ChromeDebug', 'ChromeHeadlessCI'],
    singleRun: false
  });
};

我们将不胜感激测试专家的任何帮助。

【问题讨论】:

  • 请多分享一点你的规范文件,如果你也包括测试台的配置,你可能会得到解决问题的帮助
  • 嗨 MapLion,请添加更多信息。您尝试过的应该可以工作,但是 someVariable 真的是一个属性还是只是一个变量成员?如果它是一个静态变量,只需更改值,而不是监视它。那里没有什么可窥探的。
  • 对不起,我会这样做的。从技术上讲,这不是我的项目,但我会尝试获取更多信息。我以为我添加了相关的部分。
  • @AthanasiosKataras 我该如何“改变价值”,也许这就是我所缺少的。我通常不知道如何在另一个正在测试的函数中调用某些东西。
  • 让我知道这三个是否适合您。

标签: angular testing jasmine jestjs angular8


【解决方案1】:

我可以通过三种方式了解它

直接设置值

// TODO: Fix this
it('should create service and loadDiscrepancies', () => {
  // in this example, discrepancyService constructor sets the
  // value of a variable = ApiConfigService.settings.endPoint
  // ApiConfigService.settings is static; how do I "replace"
  // the value of endPoint in a call like this so I don't get
  // an error because ApiConfigService.settings is undefined
  // when called from a service in the test?
  AppConfigService.settings = { endpoint: 'http://endpoint' }
  const spy = spyOn(discrepancyService.dispatcher, 'dispatchPayload');
  discrepancyService.loadDiscrepancies();
  expect(spy.calls.count()).toEqual(1);
});

添加空检查和设置器

@Injectable()
export class SomeService {
someVariable;
  constructor() {
    // I can't get the test to not give me a TypeError: Cannot read property 'someSettingsVariable' of undefined on this line
    if (AppConfigService && AppConfigService.settings) {
        this.someVariable = AppConfigService.settings.someSettingsVariable;
    }
  }
}

set endPoint(value) {
    this.someVariable = value
}

隐藏服务背后的静态实现

这对我来说是迄今为止最好的解决方案。与其使用静态实现,不如创建一个可以轻松监视的单个实例服务。这不仅是您可以想象的问题,而且是所有避免使用静态实现的 OOP 语言的问题。

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ConfigService {
  private endpoint: string;
  constructor() { }
  get endPoint(): string {
      return this.endPoint;
  }
}

运行时角度配置的完整示例here

【讨论】:

  • 所以您建议将其设为单例并完全取消静态实现——这就是您所说的第三个选项的意思吗?直接设置值对我不起作用,我真的不想为所有内容添加空检查和设置器。
  • 是的。创建配置服务。我添加了一些示例代码和完整解决方案的参考。
  • 因此,经过大量实验和反复试验(您的建议都没有奏效),我发现我的潜在问题是此线程中描述的竞争条件:github.com/angular/angular/issues/23279 请注意,这还包括您提供的链接,这似乎应该有效,但在我的情况下不起作用。我按照线程中的最后一个示例进行了重新设计,并且也遵循了这个:angular.io/guide/dependency-injection-providers 最终,我取消了静态变量,这导致我的测试再次工作。我会将其标记为已接受。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-08
  • 2017-12-29
  • 1970-01-01
  • 2015-07-07
相关资源
最近更新 更多