Угловое шутливое или жасминовое тестирование: как правильно шпионить / издеваться над статическим объектом, вызываемым из тестируемого класса? - PullRequest
1 голос
/ 26 октября 2019

У меня есть 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, а другой с Жасмин / Карма, и мне нужно выяснить,образец того, как заставить тест работать в этой конструкции.

Я пробовал такие вещи, как:

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
  });
};

Любая помощь от гуру тестирования будет признательна.

1 Ответ

1 голос
/ 27 октября 2019

Три способа узнать об этом

Установить значение напрямую

// 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
}

Скрыть статическую реализацию позадиуслуга

Для меня это, безусловно, лучшее решение. Вместо статической реализации создайте службу одного экземпляра, на которую можно легко следить. Это не только проблема для вас, как вы можете себе представить, но и проблема для всех языков ООП, где избегают статических реализаций.

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

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

Полный пример угловой конфигурации во время выполнения здесь

...