Skip to content

Commit

Permalink
Update testing example to use node test runner (#8829)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Cousens <[email protected]>
  • Loading branch information
borisno2 and dcousens authored Sep 25, 2023
1 parent 15d5e5d commit ee8217a
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 116 deletions.
30 changes: 17 additions & 13 deletions examples/testing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ You can also access a GraphQL Playground at [localhost:3000/api/graphql](http://

## Features

Keystone provides a testing library in [`@keystone-6/core/testing`](https://keystonejs.com/guides/testing) which helps you write tests using [Jest](https://jestjs.io/).
This example project uses this library to add tests to the [`withAuth()`](../with-auth) example project. The tests can be found in [example.test.ts](./example.test.ts)
This example project uses this library to add tests to the [`withAuth()`](../with-auth) example project. The tests can be found in [example-test.ts](./example-test.ts). This example uses the built in `getContext` API to run tests against a Keystone Context

## Recommendations when Testing Keystone

When testing Keystone it is good to focus on Access Control, Hooks, Virtual Fields, Custom GraphQL extensions. Ideally these can be broken out into unit tests that test the individual function and its output - most of the time without even needing to use the Keystone Context. For higher level, end-to-end/integration tests, where you want to test a particular use case or user flow, you can then use `getContext`. It is highly recommended that you do not switch database providers for your tests as each provider can have slightly different functionality.

### Running tests

The project's `package.json` includes a script:

```
"test": "jest"
"test": "node --loader tsx example-test.ts"
```

We can run the tests by running the command
Expand All @@ -38,16 +41,17 @@ pnpm test
which should give output ending with:

```
PASS ./example.test.ts
✓ Create a Person using the Query API (183 ms)
✓ Check that trying to create user with no name (required field) fails (116 ms)
✓ Check access control by running updateTask as a specific user via context.withSession() (198 ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 2.316 s, estimated 5 s
Ran all test suites.
✔ Create a User using the Query API (139.404167ms)
✔ Check that trying to create user with no name (required field) fails (96.580875ms)
✔ Check access control by running updateTask as a specific user via context.withSession() (193.86275ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 0.072292
```

## Try it out in CodeSandbox 🧪
Expand Down
26 changes: 0 additions & 26 deletions examples/testing/babel.config.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import path from 'path';
import path from 'node:path';
import { test, beforeEach } from 'node:test';
import assert from 'node:assert/strict';

import { resetDatabase } from '@keystone-6/core/testing';
import { getContext } from '@keystone-6/core/context';
import baseConfig from './keystone';
Expand All @@ -14,52 +17,45 @@ beforeEach(async () => {
await resetDatabase(dbUrl, prismaSchemaPath);
});

test('Create a User using the Query API', async () => {
// We can use the context argument provided by the test runner to access
// the full context API.
test('Create a User using context.query', async () => {
const person = await context.query.User.createOne({
data: { name: 'Alice', password: 'dont-use-me' },
query: 'id name password { isSet }',
});
expect(person.name).toEqual('Alice');
expect(person.password.isSet).toEqual(true);
assert.equal(person.name, 'Alice');
assert.equal(person.password.isSet, true);
});

test('Check that trying to create user with no name (required field) fails', async () => {
// The context.graphql.raw API is useful when we expect to recieve an
// error from an operation.
// the context.graphql.raw API can be useful when you expect errors
const { data, errors } = (await context.graphql.raw({
query: `mutation {
createUser(data: { password: "dont-use-me" }) {
id name password { isSet }
}
}`,
})) as any;
expect(data!.createUser).toBe(null);
expect(errors).toHaveLength(1);
expect(errors![0].path).toEqual(['createUser']);
expect(errors![0].message).toEqual(
assert.equal(data!.createUser, null);
assert.equal(errors![0].path[0], 'createUser');
assert.equal(
errors![0].message,
'You provided invalid data for this operation.\n - User.name: Name must not be empty'
);
});

test('Check access control by running updateTask as a specific user via context.withSession()', async () => {
// We can modify the value of context.session via context.withSession() to masquerade
// as different logged in users. This allows us to test that our access control rules
// are behaving as expected.

// Create some users
// seed a few users to test with
const [alice, bob] = await context.query.User.createMany({
data: [
{ name: 'Alice', password: 'dont-use-me' },
{ name: 'Bob', password: 'dont-use-me' },
],
query: 'id name',
});
expect(alice.name).toEqual('Alice');
expect(bob.name).toEqual('Bob');
assert.equal(alice.name, 'Alice');
assert.equal(bob.name, 'Bob');

// Create a task assigned to Alice
// assign a task to Alice
const task = await context.query.Task.createOne({
data: {
label: 'Experiment with Keystone',
Expand All @@ -69,12 +65,12 @@ test('Check access control by running updateTask as a specific user via context.
},
query: 'id label priority isComplete assignedTo { name }',
});
expect(task.label).toEqual('Experiment with Keystone');
expect(task.priority).toEqual('high');
expect(task.isComplete).toEqual(false);
expect(task.assignedTo.name).toEqual('Alice');
assert.equal(task.label, 'Experiment with Keystone');
assert.equal(task.priority, 'high');
assert.equal(task.isComplete, false);
assert.equal(task.assignedTo.name, 'Alice');

// Check that we can't update the task (not logged in)
// test that we can't update the task (without a session)
{
const { data, errors } = (await context.graphql.raw({
query: `mutation update($id: ID!) {
Expand All @@ -84,16 +80,17 @@ test('Check access control by running updateTask as a specific user via context.
}`,
variables: { id: task.id },
})) as any;
expect(data!.updateTask).toBe(null);
expect(errors).toHaveLength(1);
expect(errors![0].path).toEqual(['updateTask']);
expect(errors![0].message).toEqual(
`Access denied: You cannot update that Task - it may not exist`
assert.equal(data!.updateTask, null);
assert.equal(errors.length, 1);
assert.equal(errors![0].path[0], 'updateTask');
assert.equal(
errors![0].message,
'Access denied: You cannot update that Task - it may not exist'
);
}

// test that we can update the task (with a session)
{
// Check that we can update the task when logged in as Alice
const { data, errors } = (await context
.withSession({ listKey: 'User', itemId: alice.id, data: {} })
.graphql.raw({
Expand All @@ -104,11 +101,11 @@ test('Check access control by running updateTask as a specific user via context.
}`,
variables: { id: task.id },
})) as any;
expect(data!.updateTask.id).toEqual(task.id);
expect(errors).toBe(undefined);
assert.equal(data!.updateTask.id, task.id);
assert.equal(errors, undefined);
}

// Check that we can't update the task when logged in as Bob
// test that we can't update the task (with an invalid session (Bob))
{
const { data, errors } = (await context
.withSession({ listKey: 'User', itemId: bob.id, data: {} })
Expand All @@ -120,10 +117,11 @@ test('Check access control by running updateTask as a specific user via context.
}`,
variables: { id: task.id },
})) as any;
expect(data!.updateTask).toBe(null);
expect(errors).toHaveLength(1);
expect(errors![0].path).toEqual(['updateTask']);
expect(errors![0].message).toEqual(
assert.equal(data!.updateTask, null);
assert.equal(errors!.length, 1);
assert.equal(errors![0].path[0], 'updateTask');
assert.equal(
errors![0].message,
`Access denied: You cannot update that Task - it may not exist`
);
}
Expand Down
8 changes: 2 additions & 6 deletions examples/testing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,16 @@
"start": "keystone start",
"build": "keystone build",
"postinstall": "keystone postinstall",
"test": "jest --runInBand --testTimeout=60000"
"test": "node --loader tsx example-test.ts"
},
"dependencies": {
"@babel/core": "^7.21.0",
"@babel/preset-env": "^7.20.0",
"@babel/preset-typescript": "^7.21.0",
"@keystone-6/auth": "^7.0.0",
"@keystone-6/core": "^5.0.0",
"@prisma/client": "^4.16.2"
},
"devDependencies": {
"@types/node": "^18.11.14",
"babel-jest": "^29.0.1",
"jest": "^29.0.0",
"tsx": "^3.9.0",
"prisma": "^4.16.2",
"typescript": "~5.0.0"
}
Expand Down
Loading

0 comments on commit ee8217a

Please sign in to comment.