8

Using velocity/jasmine, I'm a bit stuck on how I should test a server-side method requiring that there be a currently logged-in user. Is there a way to make Meteor think a user is logged in via stub/fake ?

myServerSideModel.doThisServerSideThing = function(){
    var user = Meteor.user();
    if(!user) throw new Meteor.Error('403', 'not-autorized');
}

Jasmine.onTest(function () {
    describe("doThisServerSideThing", function(){
        it('should only work if user is logged in', function(){
            // this only works on the client :(
            Meteor.loginWithPassword('user','pwd', function(err){
                expect(err).toBeUndefined();

            });
        });
    });
});
Petrov
  • 4,200
  • 6
  • 40
  • 61

4 Answers4

5

What you could do is add users just to your test suite. You could do this by populating these users in a the server-side test script:

Something like:

Jasmine.onTest(function () {
  Meteor.startup(function() {
    if (!Meteor.users.findOne({username:'test-user'})) {
       Accounts.createUser
          username: 'test-user'
  ... etc

Then, a good strategy could be to use the beforeAll in your test to login (this is client side):

Jasmine.onTest(function() {
  beforeAll(function(done) {
    Meteor.loginWithPassword('test-user','pwd', done);
  }
}

This is assuming your test isn't logged in yet. You can make this more fancy by checking for Meteor.user() and properly logging out in an afterAll, etc. Note how you can handily pass the done callback to many of the Accounts functions.

Essentially, you don't have to mock a user. Just make sure you have the right users, with the correct roles, available in the Velocity/Jasmine DB.

Thomas Goorden
  • 280
  • 1
  • 7
  • thanks for the suggestion ! I've created users created in a fixtures file already, but are you saying I can tell Jasmine's server integration tests to wait until the user is logged in ? – Petrov Mar 03 '15 at 17:02
  • Yes, that is exactly what I'm saying. The `beforeAll` (and actually all Jasmine subfunctions) accept a `done` callback. Essentially Jasmine will wait until either the done callback is called with or without an error **OR** the timeout is reached. Since the `login...` (and other) methods have the option to add a callback *and* they hand the callback an error if something goes wrong, you can just plug in the `done` callback. – Thomas Goorden Mar 04 '15 at 11:34
  • Really useful thanks ! Can I ask where you found the documentation for this ? – Petrov Mar 04 '15 at 16:03
  • Essentially there is no good public documentation. Some guys are working on a book, I guess they'd mention some of this stuff. Once you figure out how to deal with async stuff, it gets a bit more easy to understand. (By the way, if you're still new to this, I'd recommend trying coffeescript for a while. We **love** it.) – Thomas Goorden Mar 04 '15 at 18:07
  • Cheers Thomas ! I've tried coffeescript a few times, never really caught on.... there's already far too much coffee in my life at the moment :) – Petrov Mar 04 '15 at 19:51
  • Is this solution working with latest versions of meteor and jasmine/velocity? – SStanley Nov 11 '15 at 03:49
5

Lets say you have a server side method like this:

Meteor.methods({
    serverMethod: function(){
        // check if user logged in
        if(!this.userId) throw new Meteor.Error('not-authenticated', 'You must be logged in to do this!')

       // more stuff if user is logged in... 
       // ....
       return 'some result';
    }
});

You do not need to make a Meteor.loginWithPassword before executing the method. All you got to do is stub the this.userId by changing the this context of the method function call.

All defined meteor methods are available on the Meteor.methodMap object. So just call the function with a different this context

describe('Method: serverMethod', function(){
    it('should error if not authenticated', function(){
         var thisContext = {userId: null};
         expect(Meteor.methodMap.serverMethod.call(thisContext).toThrow();
    });

    it('should return a result if authenticated', function(){
         var thisContext = {userId: 1};
         var result = Meteor.methodMap.serverMethod.call(thisContext);
         expect(result).toEqual('some result');
    });

});

EDIT: This solution was only tested on Meteor <= 1.0.x

unclelim12
  • 603
  • 1
  • 8
  • 22
1

What are you testing and why does it require a user to be logged in? Most of the methods I have that need a user object I pass the user object into. This allows me to call from a test without actually being logged in. So in the actual running of the code I would pass...

var r = myMethod(Meteor.user());

but when running from the test I would call like...

it('should be truthy', function () {
  var r = myMethod({_id: '1', username: 'testUser', ...});
  expect(r).toBeTruthy();
});
pstuart2
  • 362
  • 1
  • 5
  • thanks for your answer ! what i want to test is that this method can only be run when a user is logged in and this user has a certain role (cf. alanning:roles). i have created the user with the necessary role in a fixtures file, but i can't figure out how to test that my method is applying the role check correctly... – Petrov Mar 02 '15 at 20:58
  • Option 1. Break out your role check and have it unit tested separately, again just by passing in the different scenarios it would need to handle. That way you know that piece works. Option 2. Use a client integration test so you can get the login, then user Meteor.call("yourMethod") to call your method from the client. – pstuart2 Mar 02 '15 at 22:56
  • thanks for the advice... i guess i could take option 1 but it's not very satisfactory. option2 would be fine but it's not a meteor method, rather a private server-only method – Petrov Mar 02 '15 at 23:28
  • 1
    You can just have the meteor method just point to your method. `Meteor.methods({ clientMethodName: myServerMethod });` so it can be called by both. I use these so I can unit test my methods but also call from the client via code. – pstuart2 Mar 03 '15 at 04:46
1

I think that Meteor.server.method_handlers["nameOfMyMethod"] allows you to call/apply a Meteor method and supply this as the first parameter at least in the current version (1.3.3)

this.userId = userId;
Meteor.server.method_handlers["cart/addToCart"].apply(this, arguments);
zenWeasel
  • 1,889
  • 2
  • 22
  • 27