From c2b4d9270867e368249d865a6ea5dc4e1342640b Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Fri, 3 Jan 2020 14:21:17 +0900 Subject: [PATCH] feat(zone.js): patch jasmine.createSpyObj to make properties enumerable to be true (#34624) Close #33657 in jasmine 3.5, there is a new feature, user can pass a properties object to `jasmine.createSpyObj` ``` const spy = jasmine.createSpyObj('spy', ['method1'], {prop1: 'foo'}); expect(spy.prop1).toEqual('foo'); ``` This case will not work for Angular TestBed, for example, ``` describe('AppComponent', () => { beforeEach(() => { //Note the third parameter // @ts-ignore const someServiceSpy = jasmine.createSpyObj('SomeService', ['someFunction'], ['aProperty']); TestBed.configureTestingModule({ declarations: [ AppComponent ], providers: [ {provide: SomeService, useValue: someServiceSpy}, ] }).compileComponents(); }); it('should create the app', () => { //spyObj will have someFunction, but will not have aProperty let spyObj = TestBed.get(SomeService); }); ``` Because `jasmine.createSpyObj` will create the `aProperty` with `enumerable=false`, and `TestBed.configureTestingModule` will try to copy all the properties from spyObj to the injected service instance. And because `enumerable` is false, so the property (here is aProperty) will not be copied. This PR will monkey patch the `jasmine.createSpyObj` and make sure the new property's `enumerable=true`. PR Close #34624 --- packages/zone.js/lib/jasmine/jasmine.ts | 27 +++++++++++++++++++++ packages/zone.js/test/jasmine-patch.spec.ts | 10 ++++++++ 2 files changed, 37 insertions(+) diff --git a/packages/zone.js/lib/jasmine/jasmine.ts b/packages/zone.js/lib/jasmine/jasmine.ts index 7e9650bb6e..2ad1e3596e 100644 --- a/packages/zone.js/lib/jasmine/jasmine.ts +++ b/packages/zone.js/lib/jasmine/jasmine.ts @@ -152,6 +152,33 @@ Zone.__load_patch('jasmine', (global: any, Zone: ZoneType, api: _ZonePrivate) => return clock; }; } + + // monkey patch createSpyObj to make properties enumerable to true + if (!(jasmine as any)[Zone.__symbol__('createSpyObj')]) { + const originalCreateSpyObj = jasmine.createSpyObj; + (jasmine as any)[Zone.__symbol__('createSpyObj')] = originalCreateSpyObj; + jasmine.createSpyObj = function() { + const args: any = Array.prototype.slice.call(arguments); + const propertyNames = args.length >= 3 ? args[2] : null; + let spyObj: any; + if (propertyNames) { + const defineProperty = Object.defineProperty; + Object.defineProperty = function(obj: any, p: string, attributes: any) { + return defineProperty.call( + this, obj, p, {...attributes, configurable: true, enumerable: true}); + }; + try { + spyObj = originalCreateSpyObj.apply(this, args); + } finally { + Object.defineProperty = defineProperty; + } + } else { + spyObj = originalCreateSpyObj.apply(this, args); + } + return spyObj; + }; + } + /** * Gets a function wrapping the body of a Jasmine `describe` block to execute in a * synchronous-only zone. diff --git a/packages/zone.js/test/jasmine-patch.spec.ts b/packages/zone.js/test/jasmine-patch.spec.ts index fc30c93de0..375f63ca1b 100644 --- a/packages/zone.js/test/jasmine-patch.spec.ts +++ b/packages/zone.js/test/jasmine-patch.spec.ts @@ -77,4 +77,14 @@ ifEnvSupports(supportJasmineSpec, () => { expect(log).toEqual(['resolved']); }); }); + + describe('jasmine.createSpyObj', () => { + it('createSpyObj with properties should be able to be retrieved from the spy', () => { + const spy = jasmine.createSpyObj('obj', ['someFunction'], {prop1: 'foo'}); + expect(spy.prop1).toEqual('foo'); + const desc: any = Object.getOwnPropertyDescriptor(spy, 'prop1'); + expect(desc.enumerable).toBe(true); + expect(desc.configurable).toBe(true); + }); + }); })();