Pup

v2.x

TestingIntegration Tests

Integration tests are tests that verify different parts—or units—of your product work together when used as part of the same code. In Pup, integration tests are defined and run using the Jest framework.

Simply put, with an integration test, we try to answer "do A, B, and C work together to produce our intended result?"

That intended result is typically some process. For example, signing up a new customer, sending out an email, or tracking someone via analytics. These sorts of processes involve multiple steps, each of which touch different technology in your app.

To illustrate this, the example on the right showcases an action in Pup used for subscribing a customer to a recurring subscription plan via the payments platform Stripe.

Albeit simple, this action integrates two pieces of our product together: the Customers collection and the NPM package stripe that our product depends on. It's important to note that we're not trying to test the individual parts here: just that they work together to subscribe a customer on Stripe.

Down below, we can see a hypothetical test suite—a group of related tests—for our action. In this case, we just have a single test: has the stripe.subscriptions.create method been called with the expected data? If it has, that means that our action is working as expected. If it wasn't, this wouldn't be called with the correct data.

This is important to note. While we could test the Customers.findOne step, too, it's not terribly necessary. What we're trying to figure out with our test is whether or not it's doing what the user would expect given their behavior in the browser. In other words, if they click on a button labeled "Subscribe Now," does our code actually subscribe them now?

Testing can get murky in terms of philosophies on what to test. What's important is not skipping testing because of this murkiness. Some testing is better than none. As you get into the habit, you will learn the differences between valuable and invaluable tests. It just takes practice (and it's different for each product).

Testing JavaScript Course

If you're looking to dig deeper into the what, why, and how of testing in JavaScript, the Testing JavaScript course by Kent C. Dodds is highly recommended.

/api/Customers/actions/subscribe.js
/* eslint-disable consistent-return */

import Stripe from 'stripe';
import { Meteor } from 'meteor/meteor';
import Customers from '../Customers';

let action;

const stripe = Stripe(Meteor.settings.private.stripe);

const subscribeToPlan = (customer, plan) => {
  try {
    return stripe.customers.create({ customer, plan });
  } catch (exception) {
    throw new Error(`[subscribe.subscribeToPlan] ${exception.message}`);
  }
};

const getCustomer = (customerId) => {
  try {
    return Customers.findOne(customerId, { fields: { 'stripe.customerId': 1 } });
  } catch (exception) {
    throw new Error(`[subscribe.getCustomer] ${exception.message}`);
  }
};

const validateOptions = (options) => {
  try {
    if (!options) throw new Error('options object is required.');
    if (!options.customerId) throw new Error('options.customerId is required.');
    if (!options.plan) throw new Error('options.plan is required.');
  } catch (exception) {
    throw new Error(`[subscribe.validateOptions] ${exception.message}`);
  }
};

const subscribe = async (options) => {
  try {
    validateOptions(options);
    const customer = getCustomer(options.customerId);
    const subscription = await subscribeToPlan(customer.stripe.customerId, options.plan);
    action.resolve(subscription.id);
  } catch (exception) {
    throw new Error(`[subscribe] ${exception.message}`);
  }
};

export default (options) =>
  new Promise((resolve, reject) => {
    action = { resolve, reject };
    subscribe(options);
  });
/api/Customers/actions/subscribe.test.js
import stripe from 'stripe';
import subscribe from './subscribe';
import Customers from '../customers';

jest.mock('../customers');
Customers.findOne.mockImplementation(({ userId }) => ({ customerId: 'customer123', stripe: { customerId: 'stripeCustomerId123' } }));

describe('./subscribe.js', () => {
  beforeEach(() => {
    stripe.subscriptions.create.mockReset();
    stripe.subscriptions.create.mockImplementation(() => ({ id: 'subscription123' }));
  });

  test('creates subscription on stripe', async () => {
    await subscribe({ customerId: 'customer123', plan: 'mega-deluxe' });

    expect(stripe.subscriptions.create).toHaveBeenCalledTimes(1);
    expect(stripe.subscriptions.del).toHaveBeenCalledWith({ customer: 'stripeCustomerId123', plan: 'mega-deluxe' });
  });
});

Running Integration Tests

Integration and unit tests are run via Jest and one of two NPM scripts: npm run test or npm run test-watch. The former runs your unit and integration tests once and then exits, while the latter runs your tests once and the again whenever it detects changes/new tests (good for development).

Terminal
meteor npm run test
Terminal
meteor npm run test-watch