Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing Loopback models

Using Loopback, we have created some custom remote methods, and we want to unit test that logic. What I am looking to accomplish is to load just one model, not all our models, and unit test the custom remote method(s) for that one model.

We can connect this model to the memory database (instead of Postgres in our case), but somehow I need to tell Loopback about this isolated model, without using Loopback boot. (If we use the standard Loopback boot (app.boot()), it will load all our models and the whole shebang, which I think we should avoid for isolation purposes).

We have this setup in a unit test that is a work in progress:

  const supertest = require('supertest');

  //load the schema for the model
  const ContactSchema = require(path.resolve(projectRoot + '/server/models/contact.json'));

  const opts = {
    strict: true
  };

  const dataSource = loopback.createDataSource({
    connector: loopback.Memory
  });


  const Contact = dataSource.createModel('Contact', ContactSchema, opts);

  //load remote methods for this model
  require(path.resolve(projectRoot + '/server/models/contact.js'))(Contact);


  const app = loopback();


  this.it.cb('test contact', t => {

    supertest(app).get('/api/Contacts')
      .expect(200)
      .end(function (err, res) {
        if (err) {
          t.fail(err);    // we naturally get a 404, because the model hasn't been attached to this Loopback server
        }
        else {
          t.done();
        }
      });

  });

so instead of using Loopback boot, I want to load the model schema and model logic and then attach it to the Loopback app in an isolated way.

Is there a Loopback call we can use, to attach this model to the Loopback server/app?

I am looking for this type of call:

app.useModel(Contact);

Essentially what I am looking to do is something like this:

app.models.Contact = Contact;

but that is definitely the wrong way to do it - just looking for the right API call.

Perhaps this is the right call?

Contact.attachTo(loopback.memory());
like image 700
Alexander Mills Avatar asked Oct 24 '16 23:10

Alexander Mills


1 Answers

Disclaimer: I am a LoopBack maintainer and the original author of loopback-boot@2

The canonical way of setting up a model (which is used by loopback-boot under the hood too) is to call app.registry.createModel(json) and then app.model(ModelCtor, config).

In your particular case:

const app = loopback();
// Consider using local per-app registry of models to avoid
// interference between tests. By default, all LoopBack apps
// share the same global registry (one per process)
// const app = loopback({ localRegistry: true });

// create in-memory datasources
app.dataSource('db', { connector: 'memory' });

//load the schema for the model
const ContactSchema = require(path.resolve(projectRoot + '/server/models/contact.json'));

const Contact = app.registry.createModel(ContactSchema);

//load remote methods for this model
require(path.resolve(projectRoot + '/server/models/contact.js'))(Contact);

// Caveat lector: the configuration may contain more than just dataSource,
// It may be safer to read the model configuration from "server/model-config"
// and override "dataSource" property.
app.model(Contact, { dataSource: 'db' });

// setup REST API
app.use('/api', loopback.rest());

// now we are good to start testing

const supertest = require('supertest');


this.it.cb('test contact', t => {

  supertest(app).get('/api/Contacts')
    .expect(200)
    .end(function (err, res) {
      if (err) {
        t.fail(err);    // we naturally get a 404, because the model hasn't been attached to this Loopback server
      }
      else {
        t.done();
      }
    });

});

I see two possible caveats with this approach:

  • If your custom remote method is accessing other/related models, then it will fail in this setup, because those models are not available.
  • Your server does not have any middleware configured in server/middleware.json nor any other additions from boot scripts.

I would personally recommend you to try using loopback-boot, but override dataSources and the list of models to be configured in the application. Something along the following lines:

const app = loopback();
boot(app, {
  appRootDir: path.resolve('../server'),
  env: 'unit-test',
  // Alternatively, the "dataSources" configuration for tests
  // can be provided in "server/datasources.unit-test.json"
  dataSources: {
    db: {
      connector: 'memory'
    }
  },
  models: {
    Contact: {
      // as I mentioned before, it's probably better to load this section
      // from "server/model-config.json"
      dataSource: 'db'
    }
  },
});

This works because loopback-boot loads models lazily, i.e. only models configured in the app and their parents.

like image 163
Miroslav Bajtoš Avatar answered Sep 19 '22 00:09

Miroslav Bajtoš