What's a decent way to structure code so I can easily mock models? I ultimately want to use the Inversify dependency injection library with Objection. Here's what comes to mind:
1) Establish an interface for each model. (For plain JS, define a duck typing spec in English.)
2) Have the model classes extend Objection.Model and implement their respective interfaces.
3) For each model, establish an interface for a service that inputs and outputs objects of the model.
4) Implement each service interface, internally peddling instances of the Objection.Model subclass.
5) Abstract the instantiation of the service classes (e.g. via Inversify) so that the application is independent of service class implementations after its initial configuration.
6) Create mocks of the models and model services that conform to the established interfaces, and configure the app to use these mocks when needed (e.g. for testing).
That's a lot of machinery, so I just want to make sure I'm on the right track before starting.
Also, I'm new to the whole dependency injection thing and am not sure what an ideal solution would look like in this case. Maybe someone with DI experience might suggest a better approach or perhaps ways to tweak Objection to be more accommodating of conventions. Thanks!
Looking at this another way, it's useful to know that,
resolve() and reject() methods to simulate database results.I figure it might spark some alternative ideas!
That looks like an awesome way to stub the database and test model logic. Thanks for pointing out those methods. It might be hard to get it to behave as expected, unless the test can isolate each query within the model. I would still like to stub the models though.
Every query eventually calls the static QueryBuilder#execute() method to execute the query. You can mock that method to return anything you want. That way you can mock eager queries and not just individual database queries.
Having said that, I wouldn't mock database queries. Most of the time servers do very little besides database queries and mocking them out doesn't test anything useful. Your application may be an exception to this rule, but there's something to think about.
I would create a service layer using interfaces and DI and mock those when needed, but I would simply clear the whole database before each test and insert the needed rows using insertGraph most of the time.
__EDIT__ I meant QueryBuilder#execute() not Model.query().
Great, thanks. I'm playing with my options now. I'm having a bit of a struggle architecting the types, but will share what I settle on. Complicating factors:
Partial<ModelPublicInterface>, but some model properties are only internally-assignable (such as last-modified date), and Partial produces a type in which they are all allowable.readonly qualifier doesn't necessarily help, because it requires that properties be set only at construction (last-modified date again being an example of a problematic property, given that I'm encapsulating its assignment).I'll get something working and post here about how things turned out. I want it clean.
For unit testing and mocking of database calls I use the following:
modelsSpy = jest.spyOn(models.CourseParticipation, 'query');
modelsSpy.mockReturnValue({
eager: (): any => {
return {
findById: (queryObject: any): Partial<CourseParticipation> => {
return {
PersonID: 1,
SomeOtherKeyID: 1,
Course: {
CourseCode: '1234',
SpecialtyID: 123,
Title: 'Test course',
Sessions: [
{
Date: new Date(2019, 0, 1), // Local time
Begintime: new Date(1970, 0, 1, 13, 0, 0, 0), // Local time
},
],
},
};
},
};
},
where: (): any => {
return {
patch: (): number => {
return 1;
},
};
},
});
This works pretty well for me (objectionjs version: 1.6.8) with Jest 24.
I wonder what your code looks like? We don't have so much help from TypeScript here, so any suggestions are welcome!
Most helpful comment
For unit testing and mocking of database calls I use the following:
This works pretty well for me (objectionjs version: 1.6.8) with Jest 24.
I wonder what your code looks like? We don't have so much help from TypeScript here, so any suggestions are welcome!