An integration test checks that several parts of a system work correctly together — for example, your code and a real database, or two services talking to each other. Each part may pass its own unit tests and still disagree about how to talk to the others; an integration test is where that shows up.

What makes a good integration test

  • Real parts together — use the real database or service where you can, not a stand-in, so the connection itself is tested.
  • Slower than unit tests — starting a database or a service takes time, so these tests run in seconds rather than milliseconds.
  • Fewer of them — keep a modest number, aimed at the seams between parts; unit tests cover the details.
  • Finds mismatches — catches wrong assumptions between parts, like a field with a different name or a different date format.

The shape of an integration test

It looks like a unit test, but it sets up real parts first and tears them down afterward.

test('saving a user stores it in the database', async () => {
  // Arrange: a real (test) database, not a fake.
  const db = await startTestDatabase();

  // Act
  await saveUser(db, { name: 'Ada' });

  // Assert
  expect(await findUser(db, 'Ada')).toEqual({ name: 'Ada' });

  await db.stop();
});

Integration tests sit in the middle of the automatic testing pyramid.