简体   繁体   English

关于测试 Angular $localize (Angular 9) 的建议

[英]Advice on testing Angular $localize (Angular 9)

Example repo示例回购

The repo can be found here .可以在这里找到 repo。

Please note that this isn't a complete example, it is merely there to show issues around testing $localize .请注意,这不是一个完整的示例,它只是为了展示有关测试$localize问题。

There are 2 branches: 1. master 1. enable-localize-unit-tests - this changes the test.ts and polyfills.ts to prevent the @angular/localize/init from being imported, the $localize global function is also spied有2个分支:1. master 1. enable-localize-unit-tests -这改变了test.tspolyfills.ts防止@angular/localize/init从被导入,则$localize全局函数也被窥探

Overview概述

I have upgraded a project from Angular 8 to 9 (following the Angular update guide ), replacing any usage the I18n service (from the ngx-translation/i18n-polyfill) with Angular's new $localize function (documentation on this is pretty limited, here is the best I could find).我已经将一个项目从 Angular 8 升级到 9(遵循Angular 更新指南),用 Angular 的新$localize函数替换了I18n服务(来自 ngx-translation/i18n-polyfill)的任何使用(关于此的文档非常有限, here是我能找到的最好的)。 I can run localised builds and serve the application in a particular locale again.我可以运行本地化构建并再次在特定语言环境中提供应用程序。 However, I've hit a bit of a roadblock when it comes to unit testing.但是,在单元测试方面,我遇到了一些障碍。

Previously, when using the i18n-polyfill , the I18n service could be injected into components, etc. as follows (see I18nPolyfillComponent ):以前,在使用i18n-polyfill ,可以将I18n服务注入到组件等中,如下所示(参见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) {}
}

This could easily tested by injecting a spy into the component:这可以通过向组件中注入间谍来轻松测试:

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

However, I am unsure if similar unit tests could be written to test the usage of $localize as it is a global function rather than an injectable service.但是,我不确定是否可以编写类似的单元测试来测试$localize的使用,因为它是全局函数而不是可注入服务。

For completeness the component would look like this using $localize (see I18nLocalizeComponent ):为了完整I18nLocalizeComponent使用$localize的组件看起来像这样(参见I18nLocalizeComponent ):

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

Testing rationale测试原理

I would like to ensure that my applications are interacting with I18n / $localize appropriately (called the correct number of times, with correct parameters, etc.).我想确保我的应用程序与I18n / $localize适当的交互(调用正确的次数,使用正确的参数等)。 This just prevent silly errors if someone accidentally changes a trans-unit ID or the base translations value.如果有人不小心更改了跨单元 ID 或基本翻译值,这只是防止出现愚蠢的错误。

What I've tried我试过的

I have tried to replace the global $localize function with a spy in test.ts and avoiding importing @angular/localize/init :我试图用test.ts的间谍替换全局$localize函数并避免导入@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;
}

And then using the spied $localize in the tests (see :然后在测试中使用间谍$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);
  });
});

The spy does work but tests will fail if the component, or another component, uses the i18n directives in it's template, for example ( I18nLocalizeTemplateComponent ):间谍确实可以工作,但如果组件或其他组件在其模板中使用i18n指令,则测试将失败,例如( I18nLocalizeTemplateComponent ):

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

This will fail with the following error:这将失败并出现以下错误:

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)

Does anyone have any advice on how to handle this use case or is it currently not supported?有没有人对如何处理这个用例有任何建议,或者目前不支持? I haven't seen any documentation, tutorials or articles which help with this so any assistance would be greatly appreciated.我还没有看到任何对此有帮助的文档、教程或文章,因此我们将不胜感激。

To recreate the issue above in the example repo you'd have to:要在示例 repo 中重新创建上述问题,您必须:

  1. Uncomment code in test.ts取消注释test.ts代码
  2. Comment out import from @angular/localize/init in polyfills.tspolyfills.ts注释掉来自@angular/localize/init导入
  3. Run tests for I18nLocalizeTemplateComponentI18nLocalizeTemplateComponent运行测试

The tests could be executed by using npm run test -- --include src/app/i18n-localize-template/i18n-localize-template.component.spec.ts可以使用npm run test -- --include src/app/i18n-localize-template/i18n-localize-template.component.spec.ts来执行npm run test -- --include src/app/i18n-localize-template/i18n-localize-template.component.spec.ts

Alternatively, use enable-localize-unit-tests branch then follow step 3.或者,使用enable-localize-unit-tests分支,然后按照步骤 3。

Notes笔记

  1. The following has been added to polyfills.ts for the project as per Angular upgrade guide:根据 Angular 升级指南,以下内容已添加到项目的polyfills.ts中:
     import "@angular/localize/init";
  2. I'm currently using Karma ( 4.4.1 ) and Jasmine ( 3.5.9 ) for unit testing我目前正在使用 Karma ( 4.4.1 ) 和 Jasmine ( 3.5.9 ) 进行单元测试

Environment details环境细节

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


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

The solution that I found was to spy on the translate function of $localize rather than $localize itself: test.ts我发现的解决方案是监视$localizetranslate函数而不是$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);

Make sure you have imported @angular/localize/init in test.ts .确保您已在test.ts导入@angular/localize/init

The I18nLocalizeComponent unit tests can be updated as follows: 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);
  });
});

This change will also allow unit tests of component's with i18n tags in the template to run successfully as well.此更改还将允许模板中带有i18n标记的组件的单元测试也成功运行。

To check out the changes see the fix-localize-unit-tests branch of the example repo.要查看更改,请参阅示例存储库的fix-localize-unit-tests分支。

CANNOT find name 'global'找不到名称“全球”

For those who try to run it on Angular 12.对于那些尝试在 Angular 12 上运行它的人。

I had following issue我有以下问题在此处输入图片说明

To make the demo running I had to add the following为了使演示运行,我必须添加以下内容
-> tsconfig.spec.json add "node" to compiler options. -> tsconfig.spec.json 将“节点”添加到编译器选项。

###tsconfig.spec.json

"compilerOptions": {
types": [
"jasmine",
"node"       <====
]} 

This answer ist expanding the tests in the original posts.这个答案是扩展原始帖子中的测试。

For anyone who wants to test if对于任何想要测试是否
the $localize translates the correct strings? $localize 翻译正确的字符串?

Here is how the test looks like:这是测试的样子:

it('should call $localize.translate with "string to translate"', () => {
    expect(mockLocalize.calls.argsFor(0)[0][0]).toContain('string to translate');
  });

Here is a Repo .这是一个回购
The Repo is a "fork" of the repo given in this post.回购是这篇文章中给出的回购的“分叉”。

Differences:差异:

  • update on Angular 12更新 Angular 12
  • added the test for translation-strings添加了对翻译字符串的测试

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM