简体   繁体   中英

Mocking read-only Flux store property in TypeScript

I'm creating an app using React, Flux, and TypeScript. I'm testing using Karma and Jasmine.

I have a Store, like this:

class MemberStore {
    private _members:Member[];
    public get members():Member[] { return this._members; }
}

As with all Flux stores, data is read-only and modified through action handlers (not shown), not setters.

I would like to mock this data store in a unit test of code that depends on MemberStore . However, since members is read-only I cannot do something like this:

var mockMemberStore:MemberStore = jasmine.createSpyObj("MemberStore");
mockMemberStore.members = [/*mock members*/];

The above code actually emits working JS, since mockMemberStore is not actually an instance of MemberStore but a Jasmine spy object (and createSpyObj returns any ). However, it generates a compile-error on mockMemberStore.members = [] because it's a read-only property.

EDIT: So it turns out I was mis-reading the error, and it was not this code that gave an error. It was not a compile error, it's a runtime error. Surprisingly, TSC doesn't care if I assign a value to members at compile time, even though it's read-only. In the above example I replace MemberStore completely with a jasmine SpyObj, which works. If instead I simply try to spyOn a real version of MemberStore , that's when I see a runtime error that members cannot be assigned. Example

I could change it to var mockMemberStore:any , or use casting like (<any> mockMemberStore).members = [] , but then the compiler doesn't see members as a reference to ModelStore/members so all static type checking is lost (and things like "find references" and "refactor/rename" don't work). What is the approach to use here?

You should be able to write:

(mockMemberStore as any).members = [""];

This will not change the type of any future references to mockMemberStore . Because this is a unit test, the code does not need to be as perfect as it usually is. The important thing is that it will fail if you introduce an error, and in this case, it will fail: if you perform an invalid refactor, renamedMembers will no longer be set and the test should blow up, and you will be able to fix it.

This is not ideal, since you do lose find-usages, but it's not as bad as it often is in other languages (mocking readonly objects is notoriously cumbersome).

Interfaces are useful for this:

interface IMemberStore {
    members: Member[];
}

class MemberStore implements IMemberStore {
    private _members:Member[];
    public get members():Member[] { return this._members; }
}

class MockMemberStore implements IMemberStore {
    members: Member[];
}

Use IMemberStore in most of your application, use MemberStore as the implementation, and use MockMemberStore in your unit tests.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM