Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot set property x of [object Module] which has only a getter #24

Closed
mralbobo opened this issue Apr 28, 2020 · 22 comments
Closed

Cannot set property x of [object Module] which has only a getter #24

mralbobo opened this issue Apr 28, 2020 · 22 comments

Comments

@mralbobo
Copy link

Error

debug.js:21 TypeError: Cannot set property AnalyticsService of [object Module] which has only a getter
    at <Jasmine>
    at new MockManager (mock-manager.js:26)
    at Function.push../node_modules/ts-mock-imports/lib/import-mock.js.ImportMock.mockClass (import-mock.js:15)
    at UserContext.<anonymous> (fbdb-requests.service.spec.ts:54)
    at ZoneDelegate.invoke (zone-evergreen.js:359)
    at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (zone-testing.js:308)
    at ZoneDelegate.invoke (zone-evergreen.js:358)
    at Zone.run (zone-evergreen.js:124)
    at runInTestZone (zone-testing.js:561)
    at UserContext.<anonymous> (zone-testing.js:576)
    at <Jasmine>

Minimal code

import { ImportMock } from 'ts-mock-imports';
import * as analyticsModule from 'services/AnalyticsService';
analyticsServiceMock = ImportMock.mockClass(analyticsModule, 'AnalyticsService');

It's that mock call that mockClass call that blows up. Similarly blows up with mockOther.

Cut down module attempted to be mocked

import * as utils from "utils";

//in the future this should subscribe to the various state services, plan, project, user, etc
// for now? leach off the old analytics service
export class AnalyticsService{
	private globals = {}
	multiselect(){ ... }
}

export let analyticsService = new AnalyticsService();
window.analyticsService = analyticsService;

Other things of note

  • build: angular-cli v8
  • testing framework: jasmine 3.4

Near as I've been able to tell, you're unable to mock es6 imports because they're supposed to be readonly. Which is roughly what the above illustrates. But I figured this package figured out some way around that limitation.

@EmandM
Copy link
Owner

EmandM commented May 4, 2020

Can you try creating and assigning AnalyticsService outside of the file that exports it? MockImports works on the assumption that the class is not instantiated until after it has been mocked. The way this code works will instantiate AnalyticsService on import of the file.

@mralbobo
Copy link
Author

Revised cut down example

export class AnalyticsService{
	private globals = {}
	multiselect(){ ... }
}

Same error.

The code using it was also modified to this (whereas before is imported the instance directly):

import { AnalyticsService } from 'services/AnalyticsService';
const analyticsService = new AnalyticsService();

Not particularly surprised given that mockOther on either/ both versions (class and instance) with simple objects or other classes of those had roughly the same result.

@mralbobo
Copy link
Author

Throwing out a concept, since skimming this https://exploringjs.com/es6/ch_modules.html#_imports-are-read-only-views-on-exports seems to indicate to me that this is expected behavior with es6 imports.

It also implies though that there's likely a fairly reasonable workaround that could be done.
Starting from here https://github.com/EmandM/ts-mock-imports/blob/master/src/managers/mock-manager.ts

If I'm reading this correctly... this code (at a really high level) creates a new stub class which gets a mocked representation of each function of the original class added to it. Then the module replaces the real class with the stub class. If I'm accurate, making this work under es6 should just be a matter of ditching the stub class and instead mocking the original module. Potentially as an extra top level function (mockClassInPlace) or something. Sound sane?

@EmandM
Copy link
Owner

EmandM commented May 15, 2020

This library works on top of ES6 imports. The trick of the library is to assign to that read-only value.

stubClass is the mock of the original module. It copies all of the functions on the original module and creates no-op functions (functions that return undefined) in their place.

It is possible that your error is caused by your build process. Are you using babel to transpile by any chance? What is the structure of your dependency tree?

@mralbobo
Copy link
Author

Shouldn't be, pretty bog standard angular-cli (it gets fairly arcane behind the scenes though and changes every version. Should boil down to webpack and a custom loader) for the typescript at least. I'll see if I can't setup a cut down repro next week.

@nico1510
Copy link

which typescript version are you using? If it's 3.9 or higher it's most likely related to microsoft/TypeScript#38568

@EmandM
Copy link
Owner

EmandM commented May 19, 2020

See jasmine/jasmine#1817 (comment) for a temporary workaround that should work.

Unfortunately, this looks like a change in how typescript works that breaks the behaviour of this library.

@EmandM EmandM closed this as completed May 19, 2020
@dcermak
Copy link

dcermak commented May 19, 2020

@EmandM Could you please add a warning to the Readme about this gotcha? I got bitten by this today and spent half a day bashing my head against the desk trying to figure out why on earth my mocks are no longer doing a thing :-(. (Ranting aside: I really love this library, thanks for creating it! It really hurts to use dependency injection instead…)

@EmandM EmandM reopened this May 21, 2020
@mralbobo
Copy link
Author

So I did a quick and dirty prototype where I basically duplicated MockManager into MockManagerInPlace, bypassed the stubclass and instead directly wrote the mocked functions onto the original class. And it worked to solve my particular issue. Both on my current version of typscript and 3.9.3.

https://github.com/mralbobo/ts-mock-imports/blob/master/src/managers/mock-manager-inplace.ts

Basically the only thing changed was the replace function and commenting out the this.module replacement in the constructor. Plus some other boilerplate to expose that class.

Naturally that as written completely breaks the "unstub" concept and probably other things. But it does prove that something can be done.

@EmandM
Copy link
Owner

EmandM commented Jun 1, 2020

Thanks for this prototype. I might just be able to use this to get everything working again.

@stale
Copy link

stale bot commented Jun 21, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Mark the issue as fresh with /remove-lifecycle stale.
Thank you for your contributions.

@stale stale bot added the lifecycle/stale Issue has no further activity label Jun 21, 2020
@ben-moore
Copy link

Hit this issue as well, thought I was going mad when my mocks stopped working! @EmandM do you think you'll be able to work around this issue? Would be great to have a solution that doesn't require adding other tweaks to all our projects using ts-mock-imports :)

@stale
Copy link

stale bot commented Jun 22, 2020

/remove-lifecycle stale

@stale stale bot removed the lifecycle/stale Issue has no further activity label Jun 22, 2020
@stale
Copy link

stale bot commented Jul 12, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Mark the issue as fresh with /remove-lifecycle stale.
Thank you for your contributions.

@stale stale bot added the lifecycle/stale Issue has no further activity label Jul 12, 2020
@stale stale bot closed this as completed Jul 22, 2020
@mralbobo
Copy link
Author

/remove-lifecycle stale

@EmandM EmandM removed the lifecycle/stale Issue has no further activity label Aug 5, 2020
@EmandM EmandM reopened this Aug 5, 2020
@EmandM
Copy link
Owner

EmandM commented Aug 5, 2020

@mralbobo I can't seem to get a repro of this issue working on my machine. Do you mind sharing your tsconfig?

@mralbobo
Copy link
Author

Attached, slightly redacted

{
	"compileOnSave": false,
	"compilerOptions": {
		"baseUrl": "./",
		"outDir": "./dist/out-tsc",
		"sourceMap": true,
		"declaration": false,
		"downlevelIteration": true,
		"experimentalDecorators": true,
		"module": "esnext",
		"moduleResolution": "node",
		"importHelpers": true,
		"target": "es2015",
		"paths": {},
		"typeRoots": [
			"node_modules/@types"
		],
		"lib": [
			"es2018",
			"dom"
		],
		"resolveJsonModule": true,
		"allowSyntheticDefaultImports": true
	},
	"angularCompilerOptions": {
		"fullTemplateTypeCheck": true,
		"strictInjectionParameters": true
	}
}

@stale
Copy link

stale bot commented Sep 28, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Mark the issue as fresh with /remove-lifecycle stale.
Thank you for your contributions.

@stale stale bot added the lifecycle/stale Issue has no further activity label Sep 28, 2020
@stale stale bot closed this as completed Oct 28, 2020
@EmandM EmandM removed the lifecycle/stale Issue has no further activity label Nov 19, 2020
@EmandM EmandM reopened this Nov 19, 2020
@EmandM EmandM closed this as completed in f2cc164 Nov 19, 2020
@EmandM
Copy link
Owner

EmandM commented Nov 19, 2020

ImportMock.mockClassInPlace() has now been added to the library. This will give you the same functionality as the original if you are simply mocking functions on classes.

This new functionality cannot edit the constructor or any variables on the mocked class however, so use with caution.

@jtheory
Copy link

jtheory commented Feb 2, 2021

Hi, it seems like this issue and the limitations that come with TS 3.9 should be at the top of the readme, in large text -- otherwise this risks wasting a lot of developer time. 🙏

Typescript 3.9 introduced new functionality that blocks the key functionality of this library. With certain compilation structures, it is no longer possible to replace module exports.
There is no true workaround for this issue.

@pmiatkowski
Copy link

pmiatkowski commented Mar 25, 2021

Angular 9+ solution:
In the tsconfig.spec.json file, add to compileOptions:
"module": "commonjs",

This solved all the issues with import * as XXX and spyOn(XXX, "methodName")

@altoblond
Copy link

altoblond commented Dec 6, 2021

The solution that worked for me is this one:

I had a module exporting a static function:

export class TimeProvider {
    /**
     * return the current date in the system
     */
    public static now(): Date {
        return new Date();
    }
}

and I use to mock it like this:

import * as TimeProviderModule from '@project/fwk-lib-core';
....
let timeProviderMock;
...
beforeEach(() => {
  timeProviderMock = ImportMock.mockStaticClass(TimeProviderModule, 'TimeProvider');
}
it('test', async () => {
        const nowMock: sinon.SinonStub = timeProviderMock.mock('now', date);
...
        expect(nowMock.calledOnce).toBe(true);
})

and I changed it to this:

import * as TimeProviderModule from '@project/fwk-lib-core';
....
let mockStaticF ;
...
beforeEach(() => {
        jest.mock('@project/fwk-lib-core/dist/time/time.provider');
        mockStaticF = jest.fn().mockReturnValue(date);
        TimeProvider.now = mockStaticF;
}
it('test', async () => {
...
        expect(mockStaticF).toBeCalledTimes(1);
})

effectively not using ts-mock-imports for static functions exported in external modules...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants