I’m struggling to get a clean solution to mock the use of Noe4j GraphQL OGM using vitetest but cant quite work out the best way to do this cleanly..
I do not seem to be able to find a way to mock either the OGM generated <type>Model
class, or the OGM.prototype.model
function which is used to get the generated <type>Model
using vanilla vitest.
I managed to solve this using vitest-mock-extended
, but I feel/worry that this is a case where either I am missing some obvious way to do this with vanilla vitest, or, its a sign that I should be isolating the OGM parts and injecting more easily testable interface.
My questions are:
- Has anyone mocked OGM and is there an approach to mocking
ogm.model
using vanilla vitest (or equivalent framework) that I am missing? - for mocking the generated Model classes (ie
UserModel
), again, is there an approach using vanilla vitest I am missing? - have/would people recommend an alternative approach to create a wrapper/interface that is more easily testable / better suited for mocking and pass that into a version of UserSignup, and then have a
UserSignupWithOGM(...)
that takesOGM
and does theogm.model
call passing inUserModel
into theUserSignup
(while I cant change the generated code, I’m happy isolating the rest of the codebase from this)
A simplified version of the setup is that I have a UserSignup
that leverages a generated GraphQL function that OGM
provides. so I have (simplified)
// UserSignup.ts
// UserSignup is registered as a resolver on the graphql server (apollo)
export const UserSignup = async (
_source: unknown,
{ username, password }: { username: string, password: string },
// ogm is passed in via context by the server
{ ogm } : { ogm: OGM<ModelMap> }) => {
// ogm provides a typesafe way to get the class we can then use to exercise the graphQL API
const User = ogm.model("User")
const [existing] = await User.find({
where: {
username,
},
});
if (existing) {
throw new Error(`User with username ${username} already exists!`);
}
const { users } = await User.create({
input: [
{
username,
password,
}
]
});
// assume a bunch more here ...
return users[0].id;
}
In my test, I cannot seem to find a way to get a mocked version of ogm
so that ogm.model("User")
would give a mocked version of the UserModel
And given OGM is generating the code, cant change any types etc..
I also could not get vanilla vitest to give a mock on the UserModel
class.
the best I have found so far is to use vitest-mock-extended
(see below)
// UserSignup.test.ts
import { expect, test } from "vitest";
import { UserSignup } from "../UserSignup";
import { OGM } from "@neo4j/graphql-ogm";
import { UserModel, ModelMap } from "../../generated/ogm-types";
import { mockDeep, mock } from "vitest-mock-extended";
const UserModelMock = mock<UserModel>();
const ogm = mockDeep<OGM<ModelMap>>({ funcPropSupport: true });
ogm.model.calledWith("User").mockReturnValue(UserModelMock);
test("Test User Signup", async () => {
UserModelMock.find
.calledWith(any())
.mockReturnValue(Promise.resolve([]);
UserModelMock.create
.calledWith(any())
.mockReturnValue(Promise.resolve([ { username: 'test', password: '', id: 'new_user' }]));
const userId = await UserSignup();
expect(userId).toBe('new_user');
});
Here is the definition of OGM:
// @neo4j/graphql-ogm.d.ts
declare class OGM<ModelMap = unknown> {
constructor(input: OGMConstructor);
// etc
model<M extends T extends keyof ModelMap ? ModelMap[T] : Model, T extends keyof ModelMap | string = string>(name: T): M;
// etc
}
and UserModel:
// ../../generated/ogm-types.ts
export declare class UserModel {
public find(args?: {
where?: UserWhere;
options?: UserOptions;
selectionSet?: string | DocumentNode | SelectionSetNode;
args?: any;
context?: any;
rootValue?: any;
}): Promise<User[]>;
public create(args: {
input: UserInput[];
selectionSet?: string | DocumentNode | SelectionSetNode;
args?: any;
context?: any;
rootValue?: any;
}): Promise<UsersMutationResponse>;
public update(args: {
where?: UserWhere;
update?: UserUpdateInput;
connect?: UserConnectInput;
disconnect?: UserDisconnectInput;
create?: UserCreateInput;
connectOrCreate?: UserConnectOrCreateInput;
selectionSet?: string | DocumentNode | SelectionSetNode;
args?: any;
context?: any;
rootValue?: any;
}): Promise<UpdateUsersMutationResponse>;
public delete(args: {
where?: UserWhere;
delete?: UserDeleteInput;
context?: any;
rootValue?: any;
}): Promise<{ nodesDeleted: number; relationshipsDeleted: number }>;
public aggregate(args: {
where?: UserWhere;
aggregate: UserAggregateSelectionInput;
context?: any;
rootValue?: any;
}): Promise<UserAggregateSelection>;
}
SB217 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.