diff --git a/libs/ng-mocks/src/lib/mock-helper/mock-helper.stub.ts b/libs/ng-mocks/src/lib/mock-helper/mock-helper.stub.ts index ff3ca93992..bcc7faadd2 100644 --- a/libs/ng-mocks/src/lib/mock-helper/mock-helper.stub.ts +++ b/libs/ng-mocks/src/lib/mock-helper/mock-helper.stub.ts @@ -19,6 +19,9 @@ export default (instance: any, override: any, style?: 'get' for (const key of Object.getOwnPropertyNames(applyOverrides)) { const desc = skipProps.indexOf(key) === -1 ? Object.getOwnPropertyDescriptor(applyOverrides, key) : undefined; + if (desc && Object.prototype.hasOwnProperty.call(desc, 'value') && desc.value === undefined) { + continue; + } helperMockService.definePropertyDescriptor(correctInstance, key, desc); } diff --git a/tests/issue-4367/test.spec.ts b/tests/issue-4367/test.spec.ts new file mode 100644 index 0000000000..6569e9a5cd --- /dev/null +++ b/tests/issue-4367/test.spec.ts @@ -0,0 +1,125 @@ +import { Component, Injectable, NgModule } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +import { + MockBuilder, + MockInstance, + MockProvider, + MockRender, +} from 'ng-mocks'; + +@Injectable() +class TargetService { + public method1() { + return `${this.constructor.name}:method1:real`; + } + + public method2() { + return `${this.constructor.name}:method2:real`; + } +} + +@Component({ + selector: 'target', + template: '', +}) +class TargetComponent { + constructor(public readonly service: TargetService) {} +} + +@NgModule({ + declarations: [TargetComponent], + exports: [TargetComponent], + providers: [TargetService], +}) +class TargetModule {} + +// @see https://github.com/help-me-mom/ng-mocks/issues/4367 +describe('issue-4367', () => { + describe('MockProvider', () => { + beforeEach(() => + TestBed.configureTestingModule({ + declarations: [TargetComponent], + providers: [ + MockProvider(TargetService, { + method1: () => 'mock', + method2: undefined, // <- should be default spy + }), + ], + }), + ); + + it('overrides method1 only', () => { + const fixture = MockRender(TargetComponent); + + // method2 should be default mock + expect( + fixture.point.componentInstance.service.method1, + ).toBeDefined(); + expect( + fixture.point.componentInstance.service.method2, + ).toBeDefined(); + }); + }); + + describe('MockBuilder', () => { + beforeEach(() => + MockBuilder(TargetComponent, TargetModule).mock(TargetService, { + method1: () => 'mock', + method2: undefined, // <- should be default spy + }), + ); + + it('overrides method1 only', () => { + const fixture = MockRender(TargetComponent); + + // method2 should be default mock + expect( + fixture.point.componentInstance.service.method1, + ).toBeDefined(); + expect( + fixture.point.componentInstance.service.method2, + ).toBeDefined(); + }); + }); + + describe('MockInstance', () => { + MockInstance.scope(); + beforeEach(() => + TestBed.configureTestingModule({ + declarations: [TargetComponent], + providers: [MockProvider(TargetService)], + }), + ); + + it('overrides method1 only', () => { + MockInstance(TargetService, () => ({ + method1: () => 'mock', + method2: undefined, // <- should be default spy + })); + const fixture = MockRender(TargetComponent); + + // method2 should be default mock + expect( + fixture.point.componentInstance.service.method1, + ).toBeDefined(); + expect( + fixture.point.componentInstance.service.method2, + ).toBeDefined(); + }); + + it('overrides method1 and method2', () => { + MockInstance(TargetService, 'method1', () => 'mock'); + MockInstance(TargetService, 'method2', undefined as never); // <- breaks the things + const fixture = MockRender(TargetComponent); + + // method2 should be undefined + expect( + fixture.point.componentInstance.service.method1, + ).toBeDefined(); + expect( + fixture.point.componentInstance.service.method2, + ).not.toBeDefined(); + }); + }); +});