Testing
Note: This guide assumes you have already created a Pylon project using npm create pylon@latest
or have an existing Pylon project. We will be modifying the existing src/index.ts
file in your project.
For testing, we'll be using the Bun test runner, which is recommended for Pylon projects. However, you can use other test runners of your choice if preferred.
Important: Before running any tests, it is crucial to run bun run build
. This step ensures that the .pylon/index.js
file is generated, which includes the GraphQL API.
Always run this build command after making changes to your Pylon application and before running your tests.
If you use bun run dev
, while making changes to your application, the .pylon/index.js
file will be updated automatically.
Set Up the Basic Pylon Application
First, let's modify the src/index.ts
file to set up a basic Pylon application with an empty posts array.
import {app} from '@getcronit/pylon'
const posts: {id: number; title: string; content: string}[] = []
export default app
This sets up the foundation for our Pylon application. We've imported the app
from Pylon and initialized an empty posts
array to store our blog posts.
Implement GraphQL Query for Fetching Posts
Now, let's implement the GraphQL query to fetch posts. Modify your src/index.ts
file as follows:
import {app} from '@getcronit/pylon'
const posts: {id: number; title: string; content: string}[] = [
{
id: 1,
title: 'Hello, world!',
content: 'This is the first post'
},
{
id: 2,
title: 'Hello, Pylon!',
content: 'This is the second post'
}
]
export const graphql = {
Query: {
posts
}
}
export default app
After making these changes, run the build command:
bun run build
Test GraphQL Query for Fetching Posts
Create a new file named pylon.test.ts
in your project root and add the following test:
import {describe, expect, test} from 'bun:test'
// Make sure to run `bun run build` before running the tests
import app from './.pylon/index'
describe('GraphQL API', () => {
test('Query.posts', async () => {
const res = await app.request('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: 'query { posts { id title content } }'
})
})
expect(res.status).toBe(200)
const data = await res.json<any>()
expect(data).toEqual({
data: {
posts: [
{
id: 1,
title: 'Hello, world!',
content: 'This is the first post'
},
{
id: 2,
title: 'Hello, Pylon!',
content: 'This is the second post'
}
]
}
})
})
})
Now, run the tests:
bun test
Implement GraphQL Mutation for Creating Posts
Let's implement the GraphQL mutation to create posts. Update your src/index.ts
file:
import {app} from '@getcronit/pylon'
const posts: {id: number; title: string; content: string}[] = [
{
id: 1,
title: 'Hello, world!',
content: 'This is the first post'
},
{
id: 2,
title: 'Hello, Pylon!',
content: 'This is the second post'
}
]
export const graphql = {
Query: {
posts
},
Mutation: {
createPost: (title: string, content: string) => {
const post = {
id: posts.length + 1,
title,
content
}
posts.push(post)
return post
}
}
}
export default app
After making these changes, run the build command again:
bun run build
Test GraphQL Mutation for Creating Posts
Add the following test to your pylon.test.ts
file:
import {describe, expect, test} from 'bun:test'
// Make sure to run `bun run build` before running the tests
import app from './.pylon/index'
describe('GraphQL API', () => {
test('Query.posts', async () => {
const res = await app.request('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: 'query { posts { id title content } }'
})
})
expect(res.status).toBe(200)
const data = await res.json<any>()
expect(data).toEqual({
data: {
posts: [
{
id: 1,
title: 'Hello, world!',
content: 'This is the first post'
},
{
id: 2,
title: 'Hello, Pylon!',
content: 'This is the second post'
}
]
}
})
})
test('Mutation.createPost', async () => {
const res = await app.request('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: `
mutation {
createPost(title: "New Post", content: "This is a new post") {
id
title
content
}
}
`
})
})
expect(res.status).toBe(200)
const data = await res.json<any>()
expect(data).toEqual({
data: {
createPost: {
id: 3,
title: 'New Post',
content: 'This is a new post'
}
}
})
})
})
Run the tests:
bun test
Implement REST API GET /posts Endpoint
Now, let's implement the REST API endpoint to fetch posts. Update your src/index.ts
file:
import {app} from '@getcronit/pylon'
const posts: {id: number; title: string; content: string}[] = [
{
id: 1,
title: 'Hello, world!',
content: 'This is the first post'
},
{
id: 2,
title: 'Hello, Pylon!',
content: 'This is the second post'
}
]
export const graphql = {
// ... (previous code remains unchanged)
}
app.get('/posts', c => {
return c.json(posts)
})
export default app
After making these changes, run the build command:
bun run build
Test REST API GET /posts Endpoint
Add the following test to your pylon.test.ts
file:
import {describe, expect, test} from 'bun:test'
// Make sure to run `bun run build` before running the tests
import app from './.pylon/index'
describe('GraphQL API', () => {
// ... (previous tests remains unchanged)
})
describe('REST API', () => {
test('GET /posts', async () => {
const res = await app.request('/posts')
expect(res.status).toBe(200)
const data = await res.json<any>()
expect(data).toEqual([
{
id: 1,
title: 'Hello, world!',
content: 'This is the first post'
},
{
id: 2,
title: 'Hello, Pylon!',
content: 'This is the second post'
},
{
id: 3,
title: 'New Post',
content: 'This is a new post'
}
])
})
})
Run the tests:
bun test
Implement REST API POST /posts Endpoint
Finally, let's implement the REST API endpoint to create posts. Update your src/index.ts
file:
import {app} from '@getcronit/pylon'
const posts: {id: number; title: string; content: string}[] = [
{
id: 1,
title: 'Hello, world!',
content: 'This is the first post'
},
{
id: 2,
title: 'Hello, Pylon!',
content: 'This is the second post'
}
]
export const graphql = {
// ... (previous code remains unchanged)
}
app.get('/posts', c => {
return c.json(posts)
})
app.post('/posts', async c => {
let body: FormData
try {
body = await c.req.formData()
} catch (e) {
return new Response('Invalid form data', {
status: 400
})
}
const title = body.get('title')?.toString()
const content = body.get('content')?.toString()
if (!title || !content) {
return new Response('Title and content are required', {
status: 400
})
}
const post = {
id: posts.length + 1,
title,
content
}
posts.push(post)
return c.json(post, 201, {
'X-Custom': 'Thanks for creating!'
})
})
export default app
After making these changes, run the build command:
bun run build
Test REST API POST /posts Endpoint
Add the following tests to your pylon.test.ts
file:
import {describe, expect, test} from 'bun:test'
// Make sure to run `bun run build` before running the tests
import app from './.pylon/index'
describe('GraphQL API', () => {
// ... (previous tests remains unchanged)
})
describe('REST API', () => {
test('GET /posts', async () => {
const res = await app.request('/posts')
expect(res.status).toBe(200)
const data = await res.json<any>()
expect(data).toEqual([
{
id: 1,
title: 'Hello, world!',
content: 'This is the first post'
},
{
id: 2,
title: 'Hello, Pylon!',
content: 'This is the second post'
},
{
id: 3,
title: 'New Post',
content: 'This is a new post'
}
])
})
test('POST /posts - successful creation', async () => {
const formData = new FormData()
formData.append('title', 'REST Post')
formData.append('content', 'This post was created via REST')
const res = await app.request('/posts', {
method: 'POST',
body: formData
})
expect(res.status).toBe(201)
const data = await res.json<any>()
expect(data).toEqual({
id: 4,
title: 'REST Post',
content: 'This post was created via REST'
})
expect(res.headers.get('X-Custom')).toBe('Thanks for creating!')
})
test('POST /posts - missing title', async () => {
const formData = new FormData()
formData.append('content', 'This post has no title')
const res = await app.request('/posts', {
method: 'POST',
body: formData
})
expect(res.status).toBe(400)
const text = await res.text()
expect(text).toBe('Title and content are required')
})
test('POST /posts - invalid form data', async () => {
const res = await app.request('/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: 'Invalid Post',
content: 'This post uses JSON instead of form data'
})
})
expect(res.status).toBe(400)
const text = await res.text()
expect(text).toBe('Invalid form data')
})
})
Run the tests:
bun test
This comprehensive guide has walked you through the process of implementing and testing a Pylon application with both GraphQL and REST API endpoints. By following these steps, you've created:
- A GraphQL query to fetch posts
- A GraphQL mutation to create posts
- A REST API GET endpoint to fetch posts
- A REST API POST endpoint to create posts
You've also written tests for each of these features, ensuring that your application is working as expected. Remember to always run bun run build
after making changes to your src/index.ts
file and before running your tests. This ensures that the latest version of your application is being tested.
As you continue to develop your Pylon application, you can follow this pattern of implementing a feature, building the application, and then testing it. This approach helps maintain high code quality and ensures that your application remains reliable as it grows in complexity.
Take a look at our testing example in the Pylon repository (opens in a new tab) for a complete working example of testing a Pylon application with Bun.
Optional: Environment Variables
By default, getEnv
returns process.env
during testing. If you need to set additional environment variables for your tests, you have to pass your mocked env as the 3rd argument to app.request
:
const MOCKED_ENV = {
MY_ENV_VAR: 'my-value'
}
const res = await app.request(
'/graphql',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: 'query { posts { id title content } }'
})
},
MOCKED_ENV
)
Note
If you have disabled telemetry in your project, this will be reverted when overwriting the environment with a mocked one. Make sure to also disable telemetry in the mock environment.
const MOCKED_ENV = {
MY_ENV_VAR: 'my-value',
PYLON_DISABLE_TELEMETRY: true
}
Optional: Mocking Dependencies
In some cases, you might want to mock certain files or modules during testing. This cannot be done with the .pylon/index.js
file because it is a bundled output. Instead, you can directly import and use the ./src/index.ts
file. This approach allows you to mock dependencies and have more control over your tests.
However, when using ./src/index.ts
directly, you need to manually set up the handler
to handle GraphQL requests.
import {handler} from '@getcronit/pylon'
import app, {graphql} from './src/index'
app.use(handler({graphql}))
... your tests here ...
This setup allows you to directly import and use the ./src/index.ts
file in your tests, giving you more flexibility and control over your test environment.
If you use the config
export in your ./src/index.ts
file, you can also pass it to the handler
function:
import {handler} from '@getcronit/pylon'
import app, {graphql, config} from './src/index'
app.use(handler({graphql, config}))
... your tests here ...