Советы по тестированию Angular $ localize (Angular 9) - PullRequest
1 голос
/ 25 марта 2020

Пример репо

Репо можно найти здесь .

Обратите внимание, что это не полный пример, это просто там, чтобы показать проблемы, связанные с тестированием $localize.

Есть 2 ветви: 1. master 1. enable-localize-unit-tests - это изменяет test.ts и polyfills.ts, чтобы @angular/localize/init не был импортировано, глобальная функция $localize также отслеживается

Обзор

Я обновил проект с Angular 8 до 9 (после Angular руководство по обновлению ), заменяющее любое использование службы I18n (из ngx-translation / i18n-polyfill) на новую функцию Angular $localize (документация на это довольно ограничено, здесь - лучшее, что я мог найти). Я могу запускать локализованные сборки и снова обслуживать приложение в определенной локали. Тем не менее, я столкнулся с некоторыми препятствиями, когда дело доходит до модульного тестирования.

Ранее, при использовании i18n-polyfill, сервис I18n мог быть внедрен в компоненты, например, c. следующим образом (см. I18nPolyfillComponent):

@Component({
  selector: "app-i18-polyfill",
  template: `<h4>{{ title }}</h4>
})
export class I18nPolyfillComponent {
  readonly title: string = this.i18n({
    id: "title",
    value: "Hello World!"
  });

  constructor(private i18n: I18n) {}
}

Это можно легко проверить, введя шпион в компонент:

describe("I18nPolyfillComponent", () => {
 let component: I18nPolyfillComponent;
 let fixture: ComponentFixture<I18nPolyfillComponent>;

  let mockI18n: Spy;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ I18nPolyfillComponent ],
      providers: [
        {
          provide: I18n,
          useValue: jasmine.createSpy("I18n"),
        },
      ],
    })
      .compileComponents().then(() => {
        mockI18n = TestBed.inject(I18n) as Spy;
    });
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(I18nPolyfillComponent);
    component = fixture.componentInstance;

    mockI18n.and.callFake((def: I18nDef) => def.value);
  });

  it("should call i18n once", () => {
    expect(mockI18n).toHaveBeenCalledTimes(1);
  });
});

Однако я не уверен если аналогичные модульные тесты могут быть написаны для проверки использования $localize, поскольку это глобальная функция, а не инъекционная служба.

Для полноты компонента компонент будет выглядеть следующим образом с использованием $localize (см. I18nLocalizeComponent):

@Component({
  selector: "app-i18n-localize",
  template: `<h4>{{ title }}</h4>
})
export class I18nLocalizeComponent {
  readonly title: string = $localize `:@@title:Hello World!`;
}

Обоснование тестирования

Я хотел бы убедиться, что мои приложения взаимодействуют с I18n / $localize надлежащим образом (вызывается правильное количество раз с правильными параметрами и т. д. c.). Это просто предотвращает глупые ошибки, если кто-то случайно меняет идентификатор транс-единицы или базовое значение перевода.

То, что я пробовал

Я пытался заменить глобальный $localize работает со шпионом в test.ts и избегает импорта @angular/localize/init:

import Spy = jasmine.Spy;
import createSpy = jasmine.createSpy;

const _global: any = typeof global !== "undefined" && global;

_global.$localize = createSpy("$localize");

declare global {
  const $localize: Spy;
}

И затем использует spied $localize в тестах (см .:

describe("I18nLocalizeComponent", () => {
 let component: I18nLocalizeComponent;
 let fixture: ComponentFixture<I18nLocalizeComponent>;

  let mockI18n: Spy;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ I18nLocalizeComponent ],
    })
      .compileComponents();
  }));

  beforeEach(() => {
    $localize.calls.reset();
    $localize.and.returnValue("Hello World!);

    fixture = TestBed.createComponent(I18nLocalizeComponent);
    component = fixture.componentInstance;
  });

  it("should call $localize once", () => {
    expect($localize).toHaveBeenCalledTimes(1);
  });
});

Шпион работает, но тесты не пройдут, если компонент или другой компонент использует директивы i18n в своем шаблоне, например (I18nLocalizeTemplateComponent):

<p i18n>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean consequat.</p>

This потерпит неудачу со следующей ошибкой:

TypeError: Cannot read property 'substr' of undefined
        at <Jasmine>
        at removeInnerTemplateTranslation (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:34560:1)
        at getTranslationForTemplate (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:34582:1)
        at i18nStartFirstPass (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:34771:1)
        at ɵɵi18nStart (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:34718:1)
        at ɵɵi18n (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:35450:1)
        at I18nLocalizeTemplateComponent_Template (ng:///I18nLocalizeTemplateComponent.js:15:9)
        at executeTemplate (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11949:1)
        at renderView (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11735:1)
        at renderComponent (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:13244:1)
        at renderChildComponents (http://localhost:9877/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11538:1)
    Error: Expected undefined to be truthy.
        at <Jasmine>
        at UserContext.<anonymous> (http://localhost:9877/_karma_webpack_/src/app/i18n-localize-template/i18n-localize-template.component.spec.ts:23:23)
        at ZoneDelegate.invoke (http://localhost:9877/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
        at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9877/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)

Кто-нибудь есть какие-либо советы о том, как обрабатывать этот вариант использования или в настоящее время он не поддерживается? Я не видел никакой документации, учебные пособия или статьи, которые помогают с этим поэтому любая помощь будет принята с благодарностью.

Чтобы воссоздать проблему, описанную выше в репозитории, вам необходимо:

  1. Раскомментировать код в test.ts
  2. Закомментируйте импорт из @angular/localize/init в polyfills.ts
  3. Run te sts для I18nLocalizeTemplateComponent

Тесты могут быть выполнены с использованием npm run test -- --include src/app/i18n-localize-template/i18n-localize-template.component.spec.ts

В качестве альтернативы, используйте enable-localize-unit-tests ответвление, затем выполните шаг 3.

Примечания

  1. Следующее было добавлено к polyfills.ts для проекта согласно Angular руководству по обновлению:
    import "@angular/localize/init";
    
  2. I В настоящее время я использую Карму (4.4.1) и Жасмин (3.5.9) для модульного тестирования

Сведения об окружающей среде

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 9.0.6
Node: 13.2.0
OS: darwin x64

Angular: 9.0.6
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, localize, platform-browser
... platform-browser-dynamic, router
Ivy Workspace: Yes

Package                            Version
------------------------------------------------------------
@angular-devkit/architect          0.900.6
@angular-devkit/build-angular      0.900.6
@angular-devkit/build-ng-packagr   0.900.6
@angular-devkit/build-optimizer    0.900.6
@angular-devkit/build-webpack      0.900.6
@angular-devkit/core               9.0.6
@angular-devkit/schematics         9.0.6
@angular/cdk                       9.1.3
@ngtools/webpack                   9.0.6
@schematics/angular                9.0.6
@schematics/update                 0.900.6
ng-packagr                         9.0.3
rxjs                               6.5.4
typescript                         3.7.5
webpack                            4.41.2

1 Ответ

0 голосов
/ 26 марта 2020

Решение, которое я нашел, заключалось в том, чтобы шпионить за translate функцией $localize вместо $localize: test.ts

const _global: any = typeof global !== "undefined" && global;
const defaultFakedLocalizeTranslate: (messageParts: TemplateStringsArray,
                                      substitutions: readonly any[]) => [TemplateStringsArray, readonly any[]] =
  (messageParts: TemplateStringsArray, substitutions: readonly any[]) => [messageParts, substitutions];

_global.mockLocalize = createSpy("mockLocalize") as Spy;

declare global {
  const mockLocalize: Spy;
}

$localize.translate = mockLocalize.and.callFake(defaultFakedLocalizeTranslate);

Убедитесь, что у вас есть импортировано @angular/localize/init в test.ts.

Модульные тесты I18nLocalizeComponent могут быть обновлены следующим образом:

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ I18nLocalizeComponent ],
    })
      .compileComponents();
  }));

  beforeEach(() => {
    mockLocalize.calls.reset();

    fixture = TestBed.createComponent(I18nLocalizeComponent);
    component = fixture.componentInstance;
  });

  it('should call $localize once', () => {
    expect(mockLocalize).toHaveBeenCalledTimes(1);
  });
});

Это изменение также разрешит модульные тесты компонентов с тегами i18n в шаблоне для успешного запуска.

Чтобы ознакомиться с изменениями, см. ветку fix-localize-unit-tests примера репо.

...