Unit-testing remote methods of a strongloop loopback.io model

1,894 views
Skip to first unread message

Leonidas Kapsokalyvas

unread,
Jun 26, 2014, 4:56:53 AM6/26/14
to loopb...@googlegroups.com

I am trying to write unittests for a loopback model using jasmine. My model has the usual CRUD endpoints but I have defined a custom '/products/:id/upload' endpoint which expects a form with files.

My model looks like

'use strict';

var loopback = require('loopback');

var ProductSchema = {
    location
: {
        type
: String,
        required
: true
   
},
    version
: {
        type
: String,
        required
: true
   
},
    id
: { type: Number, id: 1, generated: true }
};

var opts = {
    strict
: true
};

var dataSource = loopback.createDataSource({
    connector
: loopback.Memory
});
var Product = dataSource.createModel('Product', ProductSchema, opts);


Product.beforeRemote('upload', function(ctx){
   
var uploader = function(req, res){
       
// parse a multipart form
        res
({
            result
:'success'
       
});
   
};
   
function createProduct(uploaderResult){
       
// create a product out of the uploaded file
        ctx
.res.send({
            result
: uploaderResult.result
       
});
   
}
    uploader
.upload(ctx.req, createProduct);
});

Product.upload = function () {
   
// empty function - all the logic takes place inside before remote
};

loopback
.remoteMethod(
   
Product.upload,
   
{
        accepts
: [{arg: 'uploadedFiles', http: function(ctx){
                       
return function() {
                           
return { files : ctx.req.body.uploadedFiles, context : ctx };
                       
};
                   
}},
                   
{arg: 'id', type: 'string'}],
        returns
: {arg: 'upload_result', type: String},
        http
: {path:'/:id/upload', verb: 'post'}
   
}
);

module.exports = Product;

My end goal is to test the logic of the "createProduct". My test looks like

'use strict';

describe
('Product Model', function(){
   
var app = require('../../app');
   
var loopback = require('loopback');
   
var ProductModel;
    beforeEach
(function(){
        app
= loopback();
        app
.boot(__dirname+'/../../'); // contains a 'models' folder
       
ProductModel = loopback.getModel('Product');
       
var dataSource = loopback.createDataSource({
            connector
: loopback.Memory
       
});

       
ProductModel.attachTo(dataSource);
   
});

    it
('should load file ', function(){
       
ProductModel.upload();
   
});
});

By calling ProductModel.upload(); ultimately I would like to trigger the before upload remote hook. I could test "createProduct" in isolation but then I would omit the fact that createProduct ends up being called as a result of upload.

So, the core question is: How do I exercise remote method hooks inside unittests ?

Thank you

Miroslav Bajtoš

unread,
Jun 26, 2014, 12:38:43 PM6/26/14
to loopb...@googlegroups.com
    it('should load file ', function(){
       
ProductModel.upload();
   
});

By calling ProductModel.upload(); ultimately I would like to trigger the before upload remote hook. I could test "createProduct" in isolation but then I would omit the fact that createProduct ends up being called as a result of upload.

So, the core question is: How do I exercise remote method hooks inside unittests ?


Inside loopback, we use supertest[1] to start a temporary web server, send HTTP requests and assert on expected response.

var request = require('supertest');

// in your "beforeEach"
var app = loopback();
// etc.

// don't forget to add REST to the app
app.use(app.rest());


it('should load file', function() {
  request(app).post('/products/id-of-existing-product/upload')
    .attach('file', 'path/to/local/file/to/upload.png')
    .expect(200)
    .end(function(err, res) {
      if (err) return done(err);
      // res is the HTTP response
      // you can assert on res.body, etc.
    });
});

Miroslav

Leonidas Kapsokalyvas

unread,
Jun 27, 2014, 5:39:23 PM6/27/14
to loopb...@googlegroups.com
Thanks. I got it working.

britztopher

unread,
Sep 15, 2014, 12:38:37 PM9/15/14
to loopb...@googlegroups.com
I still cant get unit tests to work. I keep getting a 404 instead of a 200 that I expect.  When I define app = loopback();  how does this attach my models?  It seems like I would need to do app = ('../server/server) since the server.js class exports the app module which boots up the app models.  Am I missing something?

britztopher

unread,
Sep 15, 2014, 12:48:34 PM9/15/14
to loopb...@googlegroups.com
BTW, below is my test located in server/tests/unit

describe('Contents Model Testing Suite', function(){
    
    var supertest = require('supertest');
    var loopback = require('loopback');
    var app;

    beforeEach(function(){
        app = loopback();
        app.use(loopback.rest());

    });

    it('should grab contents from contents collection', function(done){
        supertest(app).get('/contents')
            .expect(200)
            .end(function(err, res){
                if(err) return done(err);
                console.log('RESPONSE FROM TEST: ', res);
                done();

            });
    });


});


On Thursday, June 26, 2014 12:38:43 PM UTC-4, Miroslav Bajtoš wrote:

britztopher

unread,
Sep 16, 2014, 11:15:10 AM9/16/14
to loopb...@googlegroups.com
Ok so I got this working, however, I am wondering if this is the correct way to do it.  Instead of having my app = loopback(), I just go ahead and load the express/loopback app using require.  Below is my now working test on my newly created model and remote method.  

describe('Contents Model Testing Suite', function(){
    var request = require('supertest');
    var loopback = require('loopback');
    var app = require('../../server.js');

    beforeEach(function(){
//        app = loopback();
//        app.use(loopback.rest());

    });

    it('should grab contents from contents collection', function(done){
        request(app).get('/api/contents/snippet/home-headline')
            .expect(200)
            .end(function(err, res){
                if(err) return done(err);
                console.log('RESPONSE FROM TEST: ', res);
                done();

            });
    });


});

I guess since i will be using the supertest library for testing my api then I will create a file which will export the request and app variables then require them in all my tests that need them.  Just seems fragile, as if I move a test file it will break that test suite.  I wonder why the app = loopback() and app.use(loopback.rest()); doesnt work.

Miroslav Bajtoš

unread,
Sep 16, 2014, 12:21:27 PM9/16/14
to loopb...@googlegroups.com
On Tuesday, September 16, 2014 8:15:10 AM UTC-7, britztopher wrote:
Ok so I got this working, however, I am wondering if this is the correct way to do it.  Instead of having my app = loopback(), I just go ahead and load the express/loopback app using require.  

Yes, that's the correct way. FWIW, you can simplify the require line to this:

  var app = require('../..');

 I wonder why the app = loopback() and app.use(loopback.rest()); doesnt work.

That code creates a new loopback application. The server.js file creates another loopback application, these two apps do not share models. In the example you posted above, you were sending the request to an app with no models, therefore you got 404 not found response.

Miroslav
Reply all
Reply to author
Forward
0 new messages