Unit testing for AWS stepfunctions
npm install aws-stepfunction-testbash
npm install --save-dev aws-stepfunction-test
`
Then you can create a test case by in lining the stepfunction code and simulating the mock functions internally.
`javascript
const StepfunctionTester = require('../../index');
const mock = (input) => {
return {
value: input.value + 1,
}
}
describe('This is a test', () => {
test('Should work', async () => {
const step = new StepfunctionTester({
code: {
"StartAt": "Step1",
"States": {
"Step1": {
"Type": "Task",
"Resource": "arn:aws:lambda:region:account-id:function:step1",
"Next": "Step2"
},
"Step2": {
"Type": "Task",
"Resource": "arn:aws:lambda:region:account-id:function:step2",
"Next": "Step3"
},
"Step3": {
"Type": "Task",
"Next": "Done",
"Resource": "arn:aws:lambda:region:account-id:function:step3"
},
"Done": {
"Type": "Succeed"
}
}
},
lambdaResolver: () => ({ function: mock })
});
const result = await step.run({
value: 1
});
expect(result.output).toEqual({
"value": 4
})
})
})
`
$3
If you instead want to load the step-function code from a file you can place the step function code in a json file.
To load this file you can change the call to the constructor like so:
`javascript
const StepfunctionTester = require('../../index');
const mock = (input) => {
return {
value: input.value + 1,
}
}
describe('This is a test', () => {
test('Should work', async () => {
const step = new StepfunctionTester({
file: 'stepfunction.json',
lambdaResolver: () => ({ function: mock })
});
const result = await step.run({
value: 1
});
expect(result.output).toEqual({
"value": 4
})
})
})
`
$3
Often you might want to use the actual lambda functions which is used by the stepfunction.
The stepfunctions only specify the ARN or depending on if you load the json from a cloudformation template the replacement variable.
In either case you need to resolve the variable (ARN) into either (as we did above) the code or the file of the lambda function handler.
The simplest way to do this is to use the simple resolver which will strip off any control characters and try to find the files on the filesystem.
To use the simple resolver to resolve Step-1.js, s-t-e-p-2.js and step3.js:
`javascript
const StepfunctionTester = require('../../index');
describe('This is a test', () => {
test('Should work', async () => {
const step = new StepfunctionTester({
file: 'stepfunction.json',
lambdaPath: '.',
lambdaResolver: 'simple',
});
const result = await step.run({
value: 1
});
expect(result.output).toEqual({
"value": 4
})
})
})
`
Yet another way to turn a lambda ARN into a function file is to use a map:
`javascript
const lambdas = {
step1: 'step1.js'
step2: 'step2.js'
step3: 'step3.js'
}
`
This can then be used in the constructor like so:
`javascript
const StepfunctionTester = require('../../index');
const lambdas = {
step1: 'step1.js'
step2: 'step2.js'
step3: 'step3.js'
}
describe('This is a test', () => {
test('Should work', async () => {
const step = new StepfunctionTester({
file: 'stepfunction.json',
lambdas: lambdas,
lambdaResolver: 'map',
});
const result = await step.run({
value: 1
});
expect(result.output).toEqual({
"value": 4
})
})
})
`
$3
Often you do not want to integration test the entire step function instead focusing on a specific part.
This can be achieved by setting startStep and stepsCount which will set the start step and the number of steps to execute.
`javascript
const StepfunctionTester = require('../../index');
describe('This is a test', () => {
test('Should work', async () => {
const step = new StepfunctionTester({
file: 'stepfunction.json',
lambdaResolver: 'simple',
startStep: 'step2',
stepsCount: 1
});
const result = await step.run({
value: 1
});
expect(result.output).toEqual({
"value": 2
})
})
})
`
$3
It also supports mocking function (both entire functions and mocking inside lambdas) to facilitate easy testing.
`javascript
// ...
describe('This is a test', () => {
test('Should work', async () => {
const step = new StepfunctionTester({
file: 'stepfunction.json',
lambdaResolver: (arn) => ({ file: lambdas[arn]}),
mocks: {
beforeEach: () => {
// Using before each we can mock things for all States.
jest.spyOn(client, 'foo').mockImplementation(() => 'test1')
},
afterEach: () => {
jest.restoreAllMocks();
},
step1: (input) => {
// You can also create a spcific mock step for each State
if (input.flag === 'test1') {
// We can also use the input to determine what we should mock to mock different outcomes
jest.spyOn(client, 'bar').mockImplementation(() => 'test2')
} else {
jest.spyOn(client, 'bar').mockImplementation(() => 'end')
}
}
}
});
const result = await step.run({
value: 1
});
expect(result.output).toEqual({
"value": 4
})
})
})
``