- Section 10: Testing Isolated Microservices
- Table of Contents
- Scope of Testing
- Testing Goals
- Testing Architecture
- Index to App Refactor
- A Few Dependencies
- Test Environment Setup
- Our First Test
- Testing Invalid Input
- Requiring Unique Emails
- Changing Node Env During Tests
- Tests Around Sign In Functionality
- Testing Sign Out
- Issues with Cookies During Testing
- Easy Auth Solution
- Auth Helper Function
- Testing Non-Authed Requests
Whats the scope of our tests? | Example |
---|---|
Test a single piece of code in isolation | Single middleware |
Test how different pieces of code work together | Request flowing through multiple middlewares to a request handler |
Test how different components work together | Make request to service, ensure write to database was completed |
Test how different services work together | Creating a 'payment' at the 'payments' service should affect the 'orders' service |
How will we run these tests?
- We are going to run these tests directly from our terminal without using docker
- This implies that our local environment is capable of running each service!
- Simple enough now, but more complex projects might make this hard!
// app.ts
import express from 'express';
import 'express-async-errors';
import { json } from 'body-parser';
import cookieSession from 'cookie-session';
import { currentUserRouter } from './routes/current-user';
import { signinRouter } from './routes/signin';
import { signoutRouter } from './routes/signout';
import { signupRouter } from './routes/signup';
import { errorHandler } from './middlewares/error-handler';
import { NotFoundError } from './errors/not-found-error';
const app = express();
app.set('trust proxy', true);
app.use(json());
app.use(
cookieSession({
signed: false,
secure: true
})
);
app.use(currentUserRouter);
app.use(signinRouter);
app.use(signoutRouter);
app.use(signupRouter);
app.all('*', async (req, res) => {
throw new NotFoundError();
});
app.use(errorHandler);
export { app };
// index.ts
import mongoose from 'mongoose';
import { app } from './app';
const start = async () => {
if(!process.env.JWT_KEY) {
throw new Error('JWT_KEY must be defined');
}
try {
await mongoose.connect('mongodb://auth-mongo-srv:27017/auth', {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
});
console.log('Connected to MongoDb');
} catch (err) {
console.log(err);
}
app.listen(3000, () => {
console.log('Listening on port 3000!');
});
};
start();
jest supertest mongodb-memory-server
import { MongoMemoryServer } from 'mongodb-memory-server';
import mongoose from 'mongoose';
import { app } from '../app';
let mongo: any;
beforeAll(async () => {
process.env.JWT_KEY = 'asdfasdf';
mongo = new MongoMemoryServer();
const mongoUri = await mongo.getUri();
await mongoose.connect(mongoUri, {
useNewUrlParser: true,
useUnifiedTopology: true
});
});
beforeEach(async () => {
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
await collection.deleteMany({});
}
});
afterAll(async () => {
await mongo.stop();
await mongoose.connection.close();
});
// signup.test.ts
import request from 'supertest';
import { app } from '../../app';
it('returns a 201 on successful signup', async () => {
return request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(201);
});
// signup.test.ts
import request from 'supertest';
import { app } from '../../app';
it('returns a 400 with an invalid email', async () => {
return request(app)
.post('/api/users/signup')
.send({
email: 'alskdflaskjfd',
password: 'password'
})
.expect(400);
});
it('returns a 400 with an invalid password', async () => {
return request(app)
.post('/api/users/signup')
.send({
email: 'alskdflaskjfd',
password: 'p'
})
.expect(400);
});
it('returns a 400 with missing email and password', async () => {
await request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com'
})
.expect(400);
await request(app)
.post('/api/users/signup')
.send({
password: 'alskjdf'
})
.expect(400);
});
// signup.test.ts
it('disallows duplicate emails', async () => {
await request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(201);
await request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(400);
});
- cookie is set for https
- supertest is using http not https
- switch to http during test
// signup.test.ts
it('sets a cookie after successful signup', async () => {
const response = await request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(201);
expect(response.get('Set-Cookie')).toBeDefined();
});
// signin.test.ts
import request from 'supertest';
import { app } from '../../app';
it('fails when a email that does not exist is supplied', async () => {
await request(app)
.post('/api/users/signin')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(400);
});
it('fails when an incorrect password is supplied', async () => {
await request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(201);
await request(app)
.post('/api/users/signin')
.send({
email: 'test@test.com',
password: 'aslkdfjalskdfj'
})
.expect(400);
});
it('responds with a cookie when given valid credentials', async () => {
await request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(201);
const response = await request(app)
.post('/api/users/signin')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(200);
expect(response.get('Set-Cookie')).toBeDefined();
});
// signout.test.ts
import request from 'supertest';
import { app } from '../../app';
it('clears the cookie after signing out', async () => {
await request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(201);
const response = await request(app)
.post('/api/users/signout')
.send({})
.expect(200);
expect(response.get('Set-Cookie')[0]).toEqual(
'express:sess=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; httponly'
);
});
// current-user.test.ts
import request from 'supertest';
import { app } from '../../app';
it('responds with details about the current user', async () => {
await request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(201);
const response = await request(app)
.get('/api/users/currentuser')
.send()
.expect(200);
console.log(response.body);
});
import request from 'supertest';
import { app } from '../../app';
it('responds with details about the current user', async () => {
const authResponse = await request(app)
.post('/api/users/signup')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(201);
const cookie = authResponse.get('Set-Cookie');
const response = await request(app)
.get('/api/users/currentuser')
.set('Cookie', cookie)
.send()
.expect(200);
expect(response.body.currentUser.email).toEqual('test@test.com');
});
global.signin = async () => {
const email = 'test@test.com';
const password = 'password';
const response = await request(app)
.post('/api/users/signup')
.send({
email,
password
})
.expect(201);
const cookie = response.get('Set-Cookie');
return cookie;
};
it('responds with null if not authenticated', async () => {
const response = await request(app)
.get('/api/users/currentuser')
.send()
.expect(200);
expect(response.body.currentUser).toEqual(null);
});