Skip to content

Testing

Darrell Warde edited this page Aug 15, 2022 · 1 revision

Integration testing

General rules

  • Always store Driver and Session instances as describe block level variables to ensure they are accessible throughout the test file
  • Always use the Neo4j helper class to construct driver instances and create database sessions
  • Always query the database (either directly or via GraphQL) using bookmarks to ensure safety when querying against a causal cluster
  • Use toIncludeSameMembers when asserting the values of arrays - this will check for array members and length, but ignore order

Driver construction

An instance of the Neo4j driver should be constructed per test file in the beforeAll hook, and closed in the afterAll hook.

We have a helper class Neo4j located in packages/graphql/tests/integration/neo4j.ts to assist in driver construction.

⚠️ Neo4j.getDriver() must be used for driver construction.

A template test file might look as follows:

import type { Driver } from "neo4j-driver";
import Neo4j from "./neo4j";

describe("This is a test file", () => {
  let driver: Driver;

  beforeAll(async () => {
    neo4j = new Neo4j();
    driver = await neo4j.getDriver();
  });

  afterAll(async () => {
    await driver.close();
  });
});

Session management

Integration tests should be written in a way where database sessions are opened in either the beforeEach or beforeAll hook, and closed in the corresponding afterEach or afterAll hook.

⚠️ Neo4j.getSession() must be used for session creation to ensure that the test database is targeted.

beforeEach and afterEach

This should generally be preferred to ensure that test scope is fully encapsulated within a single database session. It is applicable when no test data is needed, or when test data is being created on a per-test basis.

An example test file might look as follows:

import type { Driver, Session } from "neo4j-driver";
import Neo4j from "./neo4j";

describe("This is a test file", () => {
  let neo4j: Neo4j;

  let driver: Driver;
  let session: Session;

  beforeAll(async () => {
    neo4j = new Neo4j();
    driver = await neo4j.getDriver();
  });

  beforeEach(async () => {
    session = await neo4j.getSession();
  });

  afterAll(async () => {
    await session.close();
  });

  afterAll(async () => {
    await driver.close();
  });
});

beforeAll and afterAll

This should only really be preferred if test data is being created for use throughout an entire test file.

An example might look like this:

import type { Driver, Session } from "neo4j-driver";
import Neo4j from "./neo4j";

describe("This is a test file", () => {
  let neo4j: Neo4j;

  let driver: Driver;
  let session: Session;

  beforeAll(async () => {
    neo4j = new Neo4j();
    driver = await neo4j.getDriver();
    session = await neo4j.getSession();
  });

  afterAll(async () => {
    await session.close();
    await driver.close();
  });
});

Causes of test flakiness

Array checking

The order of arrays returned from queries against the Neo4j GraphQL Library are rarely guaranteed unless specified, so using toBe() or toEqual() to check their values will result in test flakiness.

To address this, we have installed jest-extended as a dependency which includes the toIncludeSameMembers() matcher, which checks for array values and length, but ignores order.

For example:

const array = [1, 2, 3]; // assume that we don't know the order here

expect(array).toIncludeSameMembers([3, 2, 1]);

This also works for nested items, for instance:

const o = {
  nestedArray = [1, 2, 3], // assume that we don't know the order here
};

expect(o).toEqual({
  nestedArray: expect.toIncludeSameMembers([3, 2, 1]),
});

This should be our de facto standard for asserting the values of arrays unless we're 100% sure of the order.

Causal clustering

Causal clustering is how Neo4j runs in a clustered environment, which includes Aura Professional instances. This introduces the risk that if we insert test data and then query for it, it may not be present on the cluster members that a created session gets connected to. To address this, we must utilise the concept of database bookmarks to let the driver know where our data is.