Testing Form Submissions in Meteor

I've recently been asked how to test form submission using our meteor unit-testing approach. I figure this is worth a post of its own as it demonstrates how to distinguish between unit vs end-to-end testing.

The code to test looks like this:

 

Settings = new Meteor.Collection("settings");
Template.settings.events({
    'change #ga_id': function () {
        var settings = Settings.findOne({ domain: window.location.host });
        Settings.update(settings._id, {
            ga_id: document.getElementById("ga_id").value,
            domain: window.location.host
        });
    }
});

And the template looks like this:

<template name="settings">
    <h1>Settings</h1>
    <input id="ga_id" type="text" value="{{ga_id}}" />
</template>

The unit test would isolate the 'change #ga_id' event code and test just that. Here's the gist of it - untested code.. ironically (-:

// Stub the browser objects. I'll add these to the stubs soon
var window = { location: { host: ''} };
var document = { getElementById: function() {} };

Template.stub('settings');

describe("Change setting", function() {
    it("updates qa_id setting when the user changes the value", function() {

        // SETUP all your dependencies and expected values
        window.location.host = 'bar.foo.com';
        spyOn(Settings, 'findOne').andReturn({ _id: 5 });
        spyOn(Settings, 'update');
        spyOn(document, 'getElementById').andReturn( { value: 'blah' } );

        // EXECUTE the single pathway through your code you're testing.
        // in this case, you only have one. If you had more, you'd have more it("...") tests
        Template.settings.fireEvent('change #ga_id');

        // VERIFY the values made it through the spies correctly
        expect(Settings.findOne).toHaveBeenCalledWith( { domain : 'bar.foo.com' } );
        expect(document.findElementById).toHaveBeenCalledWith( 'ga_id' );
        expect(Settings.update).toHaveBeenCalledWith( 5, { ga_id : 'blah', domain'bar.foo.com' } );
    });
});

Then to make sure the template + your code + meteor + mongo are delivering the value to the user, you can do an end-to-end that looks like this:

it("Changing the settings persists when the user comes back to the site", function (done) {
    openApp().
        then(openSettings).
        then(verifyTheGaSettingIs('old setting')).
        then(changeGaTo('new setting')).
        then(openApp).
        then(openSettings).
        then(verifyTheGaSettingIs('new setting')).
        then(finish(done), error);
});