Objection.js: Mocking models and dependency injection

Created on 13 Dec 2017  路  5Comments  路  Source: Vincit/objection.js

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!

Most helpful comment

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!

All 5 comments

Looking at this another way, it's useful to know that,

  1. The model's query builder can be swapped out.
  2. Query builders have a 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:

  • Not all table rows should be exposed outside the model or model service implementation (e.g. password hash, password salt, session hash, implementation decision to store multiple values together within a single JSON column). Models don't simply expose their table columns.
  • The factory method for a model should ideally accept a JSON object of the externally visible properties, while the update method should accept a subset of this. I'm looking at using 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.
  • The set of mandatory properties at model creation may be a subset of publicly visible properties (there could be defaults), suggesting that there might be two public interfaces for each model -- one for what is publicly readable, another for instantiation requirements. The 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!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

AhmadRaza786 picture AhmadRaza786  路  3Comments

sgangwisch picture sgangwisch  路  4Comments

njleonzhang picture njleonzhang  路  4Comments

rickmed picture rickmed  路  4Comments

haywirez picture haywirez  路  3Comments