Skip to content

@angular-devkit/build-angular breaks Jasmine's mock clock #11626

Closed
@gkalpak

Description

@gkalpak

Bug Report or Feature Request (mark with an x)

- [x] bug report -> please search issues before submitting
- [ ] feature request

Command (mark with an x)

- [x] test

Versions

> node --version
v8.11.3

> npm --version
5.6.0

> ng --version
Angular CLI: 6.1.0-rc.3
Node: 8.11.3
OS: win32 x64
Angular: 6.0.9
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.7.0-rc.3
@angular-devkit/build-angular     0.7.0-rc.3
@angular-devkit/build-optimizer   0.7.0-rc.3
@angular-devkit/build-webpack     0.7.0-rc.3
@angular-devkit/core              0.7.0-rc.3
@angular-devkit/schematics        0.7.0-rc.3
@angular/cli                      6.1.0-rc.3
@ngtools/webpack                  6.1.0-rc.3
@schematics/angular               0.7.0-rc.3
@schematics/update                0.7.0-rc.3
rxjs                              6.2.2
typescript                        2.7.2
webpack                           4.9.2

OS: Windows 10

Repro steps

  1. npm install --global @angular/cli.
  2. ng new test-app && cd test-app.
  3. npm test --> Passed ✔️
  4. Add a test in src/app/app.component.spec.ts that uses jasmine.clock().install(). E.g.:
    it('should pass', () => {
      jasmine.clock().install();
      expect(true).toBe(true);
      jasmine.clock().uninstall();
    });
  5. npm test --> Fails ❌

    Error: Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?

The log given by the failure

Chrome 67.0.3396 (Windows 10.0.0) AppComponent should pass FAILED
        Error: Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?
            at UserContext.eval (webpack:///./src/app/app.component.spec.ts?:31:25)
            at ZoneDelegate.invoke (webpack:///./node_modules/zone.js/dist/zone.js?:387:26)
            at ProxyZoneSpec.onInvoke (webpack:///./node_modules/zone.js/dist/zone-testing.js?:287:39)
            at ZoneDelegate.invoke (webpack:///./node_modules/zone.js/dist/zone.js?:386:32)
            at Zone.run (webpack:///./node_modules/zone.js/dist/zone.js?:137:43)
            at runInTestZone (webpack:///./node_modules/zone.js/dist/zone-testing.js?:508:34)
Chrome 67.0.3396 (Windows 10.0.0) AppComponent should pass FAILED
        Error: Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?
            at UserContext.eval (webpack:///./src/app/app.component.spec.ts?:31:25)
            at ZoneDelegate.invoke (webpack:///./node_modules/zone.js/dist/zone.js?:387:26)
            at ProxyZoneSpec.onInvoke (webpack:///./node_modules/zone.js/dist/zone-testing.js?:287:39)
            at ZoneDelegate.invoke (webpack:///./node_modules/zone.js/dist/zone.js?:386:32)
            at Zone.run (webpack:///./node_modules/zone.js/dist/zone.js?:137:43)
            at runInTestZone (webpack:///./node_modules/zone.js/dist/zone-testing.js?:508:34)
            at UserContext.eval (webpack:///./node_modules/zone.js/dist/zone-testing.js?:523:20)
Chrome 67.0.3396 (Windows 10.0.0): Executed 4 of 4 (1 FAILED) (0 secs / 0.433 secs)
Chrome 67.0.3396 (Windows 10.0.0) AppComponent should pass FAILED
        Error: Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?
            at UserContext.eval (webpack:///./src/app/app.component.spec.ts?:31:25)
            at ZoneDelegate.invoke (webpack:///./node_modules/zone.js/dist/zone.js?:387:26)
            at ProxyZoneSpec.onInvoke (webpack:///./node_modules/zone.js/dist/zone-testing.js?:287:39)
            at ZoneDelegate.invoke (webpack:///./node_modules/zone.js/dist/zone.js?:386:32)
            at Zone.run (webpack:///./node_modules/zone.js/dist/zone.js?:137:43)
            at runInTestZone (webpack:///./node_modules/zone.js/dist/zone-testing.js?:508:34)
Chrome 67.0.3396 (Windows 10.0.0): Executed 4 of 4 (1 FAILED) (0.462 secs / 0.433 secs)

Desired functionality

No errors 😁

Mention any other details that might be useful

AFAICT, the problem is caused by incorrect order of code execution. More specifically:

  • Jasmine stores the global timing functions (such as setTimeout).
  • When calling jasmine.clock().install(), it overwrites the timing functions with mocks.
  • Before doign so, it checks whether the currently global timing functions are different than what it has stored in step 1. If so, it assumes the clock is already installed (i.e. the functions are overwritten) and throws.
  • Zone.js also overwrites the global timing functions to make them zone-aware.

In order for this to work, Zone.js must patch the global functions before Jasmine stores the "original" global functions (to later compare them and determine if the clock is already installed). For some reason, it seems that Jasmine is storing the global functions before Zone.js has patched them. Then, once we call jasmine.clock().install(), Jasmine compares the orignal, unpatched global functions with the current global functions (which have since been patched by Zone.js) and finds that they are not the same (and throws).

I have no idea why this is happening, but by trying out several versions I have found out this is broken in @angular-devkit/build-angular v0.6.8 (and all the way through v0.7.0-rc.3). The commit that broke it is 0b126f6 and more specifically this change.

Reverting the test property back to also check !chunks.some(({ name }) => name === 'polyfills') fixes the issue.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions