Using Bespoken to Automate the testing of an Amazon Alexa Skill

This tutorial shows how to put together a complete unit testing and deployment pipeline for an Alexa skill supported by an AWS Lambdas function. LA.

By the end of this tutorial, you should be able to:

  • Set up and end end Alexa Skill. backed by an AWS Lambda function
  • Set up unit testing for your Alexa Skill
  • Automate the deployment of your Lambda function

Prerequisites:

Set up the Sample Alexa Skill

Clone the sample skill from github

git clone https://github.com/alexa/skill-sample-nodejs-hello-world

from the src folder of your sample skill, install the skill’s dependencies, including the Alexa SDK for node.js.

<skill-sample-nodejs-hello-world/src> npm install

Set up the Lambda Function

  1. Package the contents of the src folder in your skill into a zip file name “helloworld.zip”. Save this zip file in the root folder of your skill (One folder above the src folder).
  2. Sign in to your AWS account at [aws.amazon.com](https://aws.amazon.com/)
  3. Make sure to select US East (N. Virginia) for US skills and EU (Ireland) for UK/DE skills. These are the only two regions supported for Alexa skills development on AWS Lambda at the time of writing this tutorial.
  4. In the landing page, select “Lambda” from Compute services
  5. Select “Create a Lambda Function”
  6. In the Select Blueprint page, select “Blank Function”
  7. In the Configure Triggers page, click on the gray dash-lined box and select Alexa Skills Kit from the dropdown menu.
  8. Click “Next” to continue
  9. Once you are in the Configure Function section. Enter the Name “HelloWorldLambda”, a description, and select “Node.js 6.10” as the runtime for your skill.
  10. In the Code Entry Type dropdown, select “Upload Zip File” and upload the zip file you created in step 1
  11. Set your handler and role:
  • Keep Handler as “index.handler”
  • In the “Role” Drop down select “Create a new custom role”, This will launch a new tab in the IAM Management Console.
  • In the “IAM Role” drop down, select “Create a new IAM Role” if not already selected. The Role Name and policy document will automatically populate.
  • Select “Allow” in the lower right corner and you will be returned to your Lambda function.

11. Keep the defaults on the Advanced settings. Select “Next” and review the settings. Then select “Create Function”:

12. In the next screen look for the “ARN” and copy it so that you can use it later. ( I usually keep this tab open and do the next step on a new tab)

Set up the Alexa Skill

  1. Sign in to the Amazon Developer Portal (Use your AWS credentials)
  2. Click on the “Alexa” button on the top menu
  3. On the next page click on the “Get Started” button
  4. Click on “Add A New Skill”, this will launch the first page of the Alexa skill configuration flow.
  5. Fill out the form:
  • Skill Type:Make sure that Custom Interaction Model is selected.
  • Language:Choose English (U.S.)
  • Name: Type HelloWorld. This is the name that will be displayed in the Alexa app for this skill.
  • Invocation Name: This is the name spoken by your users to start the skill. Type “Hello World”.
  • Select **No** for all the Global Fields( Audio Player, Video App and Render Template)

6. Click the **”Save”** button and then the **”Next”** Button

7. Interaction Model screen.

  • Intent Schema: Paste this code into the Intent Schema box
{
"intents": [
{
"intent": "HelloWorldIntent"
}
]
}
  • Custom Slot Types: Leave this empty
  • Sample Utterances: Paste these sample utterances into the Sample Utterances box
HelloWorldIntent hello
HelloWorldIntent say hello
HelloWorldIntent say hello world
  • Click “Saveto verify that your interaction model builds without any errors.
  • Once the interaction model is saved, click “Next” to move on to Configuration screen.

8. Configuration screen.

  • Choose the “AWS Lambda ARN (Amazon Resource Name)” radio button
  • Check the “North America” or “Europe” check box based on your location
  • In the text field that is displayed, paste the “ARN” that was previously assigned to our your Lambda Function.
  • Select “No” for the account linking radio button.
  • Leave the remaining fields empty and click “Save” to ensure that everything is configured correctly
  • Click “Next” to go to the “Test” screen

9. Test your skill

  • Scroll down to the “Service Simulator” section
  • Enter the word “Hello” in the “Enter Utterance” text field
  • If everything is working fine, You should see a Request and a Response on the Text areas below the field.

At this point the Skill is available for testing on any Echo device registered to your account. Or using Echosim.io as your Echo simulator.

Setting up Bespoken for automated unit testing

The folks at Bespoken have done a great job of putting together a toolkit tailored to the development of voice apps. I will be leveraging some of their tools, as well as Mocha a well known unit testing framework for JavaScript.

Set up the testing framework

Install the required modules in the src folder fo your skill

npm install mocha --save-dev
npm install bespoken-tools --save-dev

Open the “package.json” file in that folder and change the test task to the following:

"scripts": {
"test": "mocha ./test/*Test.js"
},

Create your first unit test

Create a “test” folder inside the src folder. In that folder create a new file called helloWorldTest.js and add the following code:

var bst = require('bespoken-tools');
var assert = require('assert');
var server = null;
var alexa = null;
beforeEach(function (done) {
server = new bst.LambdaServer('./index.js', 10000, true);
alexa = new bst.BSTAlexa('http://localhost:10000',
'../speechAssets/IntentSchema.json',
'../speechAssets/Utterances.txt');
server.start(function() {
alexa.start(function (error) {
if (error !== undefined) {
console.error("Error: " + error);
} else {
done();
}
});
});
});
it('Launches and says Hi', function (done) {
// Launch the skill via sending it a LaunchRequest
alexa.launched(function (error, payload) {
// Check that the introduction is play as outputSpeech
assert.equal(payload.response.outputSpeech.ssml, '<speak> Hello World! </speak>');
      // Emulate the user saying 'Play'
alexa.spoken('Hello', function (error, payload) {
assert.equal(payload.response.outputSpeech.ssml, '<speak> Hello World! </speak>');
done();
});
});
});
afterEach(function(done) {
alexa.stop(function () {
server.stop(function () {
done();
});
});
});
```

The “beforeEach” method initializes and launch the emulators for the lambda server and Alexa skill.

The first test validates that the skill responds correctly to a basic launch request.

The second test validates the correct response to a “Hello” request.

The “afterEach” method shuts down the emulators. to avoid conflicts on following tests runs.

Run the test from the src folder of your skill

npm test

Set up command line access to the AWS Lambda service

Follow the instructions on the AWS site to set up the AWS CLI on your machine

Amazon CLI Set up

We will use the function “UpdateFunctionCode” to push a new zip file to the AWS environment.

BASH
update-function-code
--function-name <value>
[--zip-file <value>]
[--s3-bucket <value>]
[--s3-key <value>]
[--s3-object-version <value>]
[--publish | --no-publish]
[--dry-run | --no-dry-run]
[--cli-input-json <value>]
[--generate-cli-skeleton <value>]
```

For this tutorial, we are only interested on the following options:

— function-name: is the name given to your lambda function. In our case it should be “HelloWorldLambda” 
 — zip-file: specifies the path to the deployment package. The prefix fileb:\\ is required.
 — dry-run runs the command without making any changes to the code in AWS.
 — region: specifies the region that the command will run against, you need to use us-east-1 for the US or eu-west-1 For Europe, depending on where you created your lambda function earlier.

Build deployment command for your lambda

To deploy your skill via the command line you need a zip file containing your skill’s code (Initially we will reuse the zip file created earlier)

From the root folder of your skilll, enter the following command in your command line. (This is the windows version with the region set to us-east-1)

aws lambda update-function-code --function-name "HellowWorldLambda" -file "fileb://helloworld.zip" --dry-run --region us-east-1

If everything goes well you should see an output similar to the one below

{
"FunctionName": "HelloWorldLambda",
"FunctionArn": "<your skill arn>",
"Runtime": "nodejs6.10",
"Role": "<your lambda role>",
"Handler": "index.handler",
"CodeSize": 8147711,
"Description": "<your skill description>",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "<timestamp>",
"CodeSha256": "<hash value>",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
}
}

Test a code change and deployment via the command line

Go into the index.js file of your skill, find the line that defines the skill response

'SayHello': function () {
this.emit(':tell', 'Hello World!');
}

and change it to

'SayHello': function () {
this.emit(':tell', 'Hello New World!');
}

Change your test assertions to validate the change works as expected

it('Launches and says Hi', function (done) {
// Launch the skill via sending it a LaunchRequest
alexa.launched(function (error, payload) {
// Check that the introduction is play as outputSpeech
assert.equal(payload.response.outputSpeech.ssml, '<speak> Hello New World! </speak>');
        // Emulate the user saying 'Play'
alexa.spoken('Hello', function (error, payload) {
assert.equal(payload.response.outputSpeech.ssml, '<speak> Hello New World! </speak>');
done();
});
});
});

run your test again

<skillpath\src> npm test

Once the test pass, compress the contents of your “src” folder into a zip file again and run the “update-function-code” command without the — dry-run option:

aws lambda update-function-code --function-name "HellowWorldLambda" -file "fileb://helloworld.zip" --dry-run --region us-east-1

Once the command has finish uploading your new code, you can test the change by using the Service Emulator at the Alexa developer console, or using Echosim.io tool.

Setting up the build and deploy loop

Since we have automated unit testing and command line access to the AWS Lambda service, we can wire it all together in a build and deploy script.

I usually move between windows and linux, so I wanted this tasks to be portable across environments. I am using the npm modules rimraf and bestzip to ensure cross platform support.


Install the npm modules

npm install --save-dev bestzip
npm install --save-dev rimraf

Add the zip task to your package.json file

"scripts": {
"test": "mocha ../test/*Test.js",
"zip": "bestzip ../helloworld.zip ./*"
},

Remove the helloworld.zip file created earlier, and run the zip task

<PATH TO YOUR SKILL>/src> npm run zip 

This should create a new helloworld.zip file in the root folder of your skill.

Add the delete-zip task to your package.json file

"scripts": {
"test": "mocha ../test/*Test.js",
"zip": "bestzip ../helloworld.zip ./*",
"delete-zip": "rimraf ../helloworld.zip"
}

Run the delete-zip task

<PATH TO YOUR SKILL>/src> npm run delete-zip

This should remove the zip file created with the zip task.

Chaining the tasks

To chain the **delete-zip** and **zip** tasks, we are going to take advantage of the pre and post hooks in the npm run-scripts command.

Add a new prezip task to your package.json** file like shown below

"scripts": {
"test": "mocha ../test/*Test.js",
"delete-zip": "rimraf helloworld.zip",
"prezip": "npm run delete-zip",
"zip": "bestzip helloworld.zip ./*"
},

This will ensure that our delete-zip task runs before our zip task so that we always have a fresh zip file.

Run our tests before ziping the code

modify your prezip to run the test task before packaging the zip.

"scripts": {
"test": "mocha ../test/*Test.js",
"delete-zip": "rimraf helloworld.zip",
"prezip": "npm run delete-zip && npm run test",
"zip": "bestzip helloworld.zip ./*",
},

Add the deployment task to our build scripts

Add the deploy tasks to your package.json file:

  • The pre-deploy task runs the "package" tasks to ensure that we have run a full build in our code.
  • The deploy task executes the aws "update-function-code" to push the new "helloworld.zip" file to AWS
"scripts": {
"test": "mocha ../test/*Test.js",
"delete-zip": "rimraf helloworld.zip",
"prezip": "npm run delete-zip",
"zip": "bestzip helloworld.zip ./*",
"package": "npm run test && npm run zip",
"predeploy": "npm run package",
"deploy": "aws lambda update-function-code --function-name \"TestAlexaSkill\" --zip-file \"fileb://../helloworld.zip\" --region us-east-1"
},

Note: You need to make sure that you escape quotes and slashes in your file path as necessary.

At this point your build script will let you run unit tests in your lambda function using the Alexa API, package your code and deploy it to AWS lambda.

If you like this article, please or share it! You can also, follow me on Medium or follow me on Twitter

Like what you read? Give Ramon Davila a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.