serverless-dynamodb-local
serverless-dynamodb-local copied to clipboard
Rollbacks for Unit Testing
Im using serverless-dynamodb-local to do some unit testing.
After each test I need to clear out all the db tables ready for the next test. In other framework and DBs I've been able to 'rollback' all the changes after each test, therefore starting with a clean set of tables.
Is there any way to perform rollbacks?
Im currently having to rebuild the database before every test and its really slowed up my tests.
@GazEdge Have you tried using -inMemory flag when starting DynamoDB local. This way after the test execution if you stop and start the database, the database should be cleared.
-inMemory — DynamoDB runs in memory instead of using a database file. When you stop DynamoDB, none of the data is saved. You can't specify both -dbPath and -inMemory at once.
Yeah tried that - takes way too long to start up the server again after every test. My workaround currently is to delete every table and re-create it. But that is also a little slow and long term will be a pain to manage
@GazEdge here is my workaround for that:
1. Create a plugin lib/serverless-jest-dynamodb/index.js
- The purpose of this file is to run jest through Serverless. So I can have the tables definition and pass them as environment variables to my tests and create the tables there.
const jest = require("jest").runCLI;
class ServerlessPlugin {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
this.commands = {
test: {
usage: "Run unit tests",
lifecycleEvents: ["run"]
}
};
this.hooks = {
"test:run": this.runTests.bind(this)
};
}
get dynamodbTables() {
const { Resources } = this.serverless.service.resources;
return Object.values(Resources)
.filter(resource => resource.Type === "AWS::DynamoDB::Table")
.map(resource => resource.Properties);
}
async runTests() {
const DYNAMODB_TABLES = JSON.stringify(this.dynamodbTables);
Object.assign(process.env, {
...this.serverless.service.provider.environment,
DYNAMODB_TABLES,
NODE_ENV: "test"
});
return jest({ watch: this.options.watch, runInBand: true }, [
this.serverless.config.servicePath
]);
}
}
module.exports = ServerlessPlugin;
2. Create a dynamo client lib/dynamodb-client/index.js
- The purpose of this file is to expose a dynamodb client which knows to connect to localhost or cloud depending on
NODE_ENV
import AWS from "aws-sdk";
var isOffline =
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
export const options = {
region: "localhost",
endpoint: "http://localhost:8000"
};
export const doc = isOffline
? new AWS.DynamoDB.DocumentClient(options)
: new AWS.DynamoDB.DocumentClient();
export const raw = isOffline ? new AWS.DynamoDB(options) : new AWS.DynamoDB();
3. Create a test-utils/global-setup.js
- The purpose of this file is to initialize the dynamodb local process after all tests.
const DynamoDBLocal = require("dynamodb-local");
const path = require("path");
DynamoDBLocal.configureInstaller({
installPath: path.join(process.cwd(), ".dynamodb")
});
module.exports = () => {
console.log("\n");
return DynamoDBLocal.launch(8000, null, ["-inMemory"]);
};
4. Create a test-utils/global-teardown.js
- The purpose of this file is to stop the dynamodb local process.
const DynamoDBLocal = require("dynamodb-local");
module.exports = () => {
return DynamoDBLocal.stop(8000);
};
5. Create a test-utils/test-setup.js
- The purpose of this file is to create tables before each test and then remove it after the test passes.
import { raw, options } from "lib/dynamodb-client";
const DYNAMODB_TABLES = JSON.parse(process.env.DYNAMODB_TABLES);
beforeEach(async () => {
try {
await Promise.all(
DYNAMODB_TABLES.map(table => raw.createTable(table).promise())
);
} catch (error) {
console.log("Error creating tables");
console.log(error.message);
}
});
afterEach(async () => {
try {
await Promise.all(
DYNAMODB_TABLES.map(({ TableName }) =>
raw.deleteTable({ TableName }).promise()
)
);
} catch (error) {
console.log("Error removing tables");
console.log(error.message);
}
});
6. Create functions/profile/create-profile/create-profile.test.js
- The purpose of this file is to write the spec for the function
import createProfile from ".";
import event from "./event.json";
test("function:createProfile", async () => {
const { profile } = await createProfile(event);
expect(profile.profileId).toEqual(expect.any(String));
expect(profile).toMatchObject(event.arguments.profile);
});
7. Create functions/profile/create-profile/index.js
- The purpose of this file is to write the function code
import { doc } from "lib/dynamodb-client";
import uuid from "uuid";
const createProfile = async (event, context) => {
const profile = {
profileId: uuid.v1(),
// userId: cognito...
...event.arguments.profile
};
const params = {
TableName: process.env.PROFILES_TABLE,
Item: profile
};
await doc.put(params).promise();
return { profile };
};
export default createProfile;
Hope it helps you :)