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
This commit is contained in:
JiaLiPassion 2020-01-03 14:21:17 +09:00 committed by Andrew Kushnir
parent a87951a28f
commit c2b4d92708
2 changed files with 37 additions and 0 deletions

View File

@ -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.

View File

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