Front-End Web & Mobile

Introducing template evaluation and unit testing for AWS AppSync resolvers

AWS AppSync is a managed serverless GraphQL service that simplifies application development by letting you create a flexible API to securely access, manipulate, and combine data from one or more data sources with a single network call.

In GraphQL, developers write resolvers, units of code that implement the business logic that controls how to fetch data from data sources. Resolvers provide the runtime to fulfill GraphQL such as queries, mutations, or subscriptions with the right data.

AWS AppSync leverages Apache VTL (Velocity Template Language) to provide a lean and fast compute layer to resolve GraphQL fields. This enables developers to customize the behavior, as well as apply logic and conditions before and after communicating with the data source. AWS AppSync provides flexible integrated VTL utilities that allow developers to parse (e.g., $util.parseJson), convert (e.g., $util.toJson), generate (e.g., $util.autoId and $util.autoUlid), and log data (e.g., $util.log). AWS AppSync lets developers write all of their business logic using VTL in resolvers, as well as use Direct Lambda Resolvers, where they bypass VTL and use AWS Lambda functions to write their business logic directly.

Today, we’re releasing a new API command for AWS AppSync, EvaluateMappingTemplate, that allows customers to evaluate their AWS AppSync resolver and AWS AppSync function mapping templates. Evaluating a mapping template lets you use mock data to see how both the request and the response templates evaluate without actually running against a data source. Previously, this functionality was only available in the AWS AppSync console. Developers can now access this functionality remotely by using the latest version of the AWS Command Line Interface (AWS CLI), or by using the latest version of the AWS SDKs. Developers can leverage the EvaluateMappingTemplate command to write unit tests that verify the behavior of their resolvers in their favorite testing frameworks.

In this post, we cover the three main approaches that EvaluateMappingTemplate can be used to verify the behavior of your resolver, from both the AWS Console and your local environment. We also cover how these tests can be integrated with test suites like Jest Testing Framework.

Architecture

Architecture shows how users and clients can interact with the newly released API.

Figure 1: Architecture diagram

Example application overview

To illustrate the testing of resolvers, we use a simple Task API in AWS AppSync. This is a basic AWS AppSync API for creating calendar events with user comments.

Go to the AWS AppSync Console and create a new API with the console wizard using the Tasks App sample project. Give your API a name, such as Tasks API, and select Create. An AWS CloudFormation template is deployed in your account with a pre-confirmed AWS AppSync GraphQL API, an Amazon Cognito user pool associated with it, and a linked Amazon DynamoDB table to store data.

AWS AppSync console Getting Started page shows how to create a Tasks App

Figure 2: AWS AppSync console Getting Started page to create a Tasks App

Create the necessary users in the Cognito user pool to interact with the Tasks API. Refer to the Cognito documentation for more information on how to create users.

Now we’ll add some business logic to the resolvers. Back at the AWS AppSync Console, select the Schema section on the left navigation menu. On the right side, under Resolvers, search for Mutation and choose TaskDynamoDataSource pointing to to createTask(…): Task Mutation.

AWS AppSync console Schema page shows how to search and choose the Resolver

Figure 3: AWS AppSync console Schema page to choose and edit the Resolver

In this case, we’ll add a conditional check to evaluate the description field. If the user supplies a description field value during the input, then the logic retains the user input – if not, then the logic concatenates the title, status, and department fields to create a new description.

Leave the default Response Mapping Template as is, replace the Request Mapping Template with the following snippet, and select Save Resolver at the top of the page.

{
    "version": "2017-02-28",
    "operation": "PutItem",
    "key": {
        "id": { "S": "$util.autoId()"}
    },
    "attributeValues": {
        "title": { "S": "$context.arguments.title" },
        "classification": { "I": "$context.arguments.classification" },
        "department": { "S": "$context.arguments.department" },
        "priority": { "S": "$context.arguments.priority" },
        "taskStatus": { "S": "$context.arguments.taskStatus" },
        #if($util.isNullOrEmpty($context.arguments.description))
            "description": { "S": "$context.arguments.title for $context.arguments.department department with status $context.arguments.taskStatus" }
        #else
            "description": { "S": "$context.arguments.description" }
        #end
    }
}

Unit testing the AWS AppSync Resolvers

There are three ways that you can unit test the resolver logic with mock data without having the resolver communicate with the actual data source.

Prerequisites

The AWS Identity and Access Management (IAM) user or role used in the tests should require the following permission to execute the EvaluateMappingTemplate command.

{
    “Version”: “2012-10-17”,
    “Statement”: [
        {
            “Effect”: “Allow”,
            “Action”: “Appsync:evaluateMappingTemplate”,
            “Resource”: “arn:aws:appsync:<region>:<account>:*”
        }
    ]
}

First approach – AWS Console

The AWS AppSync console provides tools to create a GraphQL request and response with mock data, as well as test the resolver visually.

When a GraphQL resolver is invoked, it contains a context object that has relevant information about the request for you to program against. This includes arguments from a client, identity information, and data from the parent GraphQL field. It also has results that are made available to the response template. When writing or editing a resolver function, you can pass a mock context object in the console editor. This enables you to see how both the request and the response templates evaluate without actually running against a data source.

In the AWS AppSync console, again click on the  createTask(…): Task Mutation. At the top of the page, choose Select test context, and choose Create new context. In the Configure test context, select Context with Cognito User Pools Auth, and then enter a name.

Replace the Execution context with the following snippet and select Save. The purpose of the following test is to verify whether or not the description field in the result contains a concatenated string of title, status, and department fields when the description field in the input is empty or not present.

 {
    "arguments": {
        "owner": "janed",
        "title": "Important Task",
        "taskStatus": "in Progress",
        "priority": "high",
        "department": "Engineering",
        "classification": 2
    },
    "source": {},
    "result": {
        "owner": "janed",
        "title": "Important Task",
        "taskStatus": "in Progress",
        "description": "Important Task for Engineering department with status in Progress",
        "priority": "high",
        "department": "Engineering",
        "classification": 2
    },
    "identity": {
        "sub": "uuid",
        "issuer": "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_7kg7feP9Z",
        "username": "janed",
        "claims": {},
        "sourceIp": [
            "x.x.x.x"
        ],
        "defaultAuthStrategy": "ALLOW"
    }
}
AWS AppSync Console Page shows how Create and Configure text context for Resolver

Figure 4: Create and Configure text context for Resolver

To evaluate your resolver using this mocked context object, choose Run Test. In the Evaluated request mapping template section, you can observe that the test executed successfully and the description is a concatenated string of title, department, and status as specified in the Request mapping template logic.

AWS AppSync console showing the results of Evaluated request mapping template

Figure 5: AWS AppSync console showing the results of Evaluated request mapping template

Running this test lets you verify the logic in the resolver via the AWS AppSync Console.

Second approach – AWS CLI

We’ll use the new API EvaluateMappingTemplate to remotely test a mapping template with mock data.

Prerequisites

Install and configure the latest version of the AWS CLI.

Testing with the API

We’ll test the same context and mapping that we used in the first approach. In your local computer, save the request template to a file named template.vtl, and save the context objects as context.json.

Execute the following command in the command prompt:

aws appsync evaluate-mapping-template —template file:// template.vtl —context file://context.json

You get the following response:

{
    "evaluationResult": "{\n    \"version\": \"2017-02-28\",\n    \"operation\": \"PutItem\",\n    \"key\": {\n        \"id\": { \"S\": \"775a1dbb-e18b-4e24-8798-f8c87c4b5049\"}\n    },\n    \"attributeValues\": {\n        \"title\": { \"S\": \"Important Task\" },\n        \"classification\": { \"I\": \"2\" },\n        \"department\": { \"S\": \"Engineering\" },\n        \"priority\": { \"S\": \"high\" 
},\n\t\t\"taskStatus\": { \"S\": \"in Progress\" },\n\t\t            \"description\": { \"S\": \"Important Task for Engineering department with status in Progress\" }\n            }\n}"
}

The evaluation result contains the evaluated request mapping template. In this above result, the description field is a concatenation of title, status, and department, which is the expected behavior from the Resolver logic. This means that you can unit test your resolver logic from your local workstation via the AWS AppSync APIs.

Third approach – AWS SDK

We’re also introducing the EvaluateMappingTemplate method in the AWS SDK to remotely test your mapping templates with mocking data, as well as integrate with your preferred testing frameworks.

Prerequisites

Utilize the following prerequisites:

  • Install and configure AWS SDK V2.
  • Install NodeJs runtime
  • We test the same context and mapping that we used in the first approach using AWS SDK for Javascript.

Save the following snippet as index.js in your local workstation.

const AWS = require('aws-sdk')
const client = new AWS.AppSync({ region: 'us-west-2' })
const fs = require('fs');

const template = fs.readFileSync('./template.vtl', 'utf8')
const context = fs.readFileSync('./context.json', 'utf8')

client
  .evaluateMappingTemplate({ template, context })
  .promise()
  .then((data) => console.log(data))  

Execute the following command in the command prompt. Try with both of the context files and compare the results.

node index.js
{
  evaluationResult: '{\r\n' +
    '    "version": "2017-02-28",\r\n' +
    '    "operation": "PutItem",\r\n' +
    '    "key": {\r\n' +
    '        "id": { "S": "39832134-9086-4640-8590-c0c9db79ae77"}\r\n' +
    '    },\r\n' +
    '    "attributeValues": {\r\n' +
    '        "title": { "S": "Important Task" },\r\n' +
    '        "classification": { "I": "2" },\r\n' +
    '        "department": { "S": "Engineering" },\r\n' +
    '        "priority": { "S": "high" },\r\n' +
    '\t\t"taskStatus": { "S": "in Progress" },\r\n' +
    '\t\t            "description": { "S": "Important Task for Engineering department with status in Progress" }\r\n' +
    '            }\r\n' +
    '}'
}

With the AWS SDK, you can easily incorporate tests in your favorite test suite to validate your template behavior. For example, you can create a test by leveraging the Jest testing framework:

Prerequisites

Utilize the following prerequisites to follow along:

  • Install and configure AWS SDK V2.
  • Install and configure Jest package
  • Set up a new npm local project with npm init –yes
    • Install jest with npm
    • install jest npm install jest
    • Add the following command to the package.json
{  
"scripts": {
    "test": "jest"
  }
}

We’ll test the same context and mapping that we used in the first approach using AWS SDK for Javascript.

Save the following snippet as resolver.test.js in your local workstation:

const AWS = require('aws-sdk')

const fs = require('fs')
const client = new AWS.AppSync({ region: 'us-west-2' })

const template = fs.readFileSync('./template.vtl', 'utf8')
const context = fs.readFileSync('./context.json', 'utf8')
const contextJSON = JSON.parse(context)

test('Evaluate the Resolvers', async () => {
  const response = await client.evaluateMappingTemplate({ template, context }).promise()
  const result = JSON.parse(response.evaluationResult)
  expect(result.attributeValues.description.S).toEqual(
    contextJSON.arguments.title +
      ' for ' +
      contextJSON.arguments.department +
      ' department with status ' +
      contextJSON.arguments.taskStatus
  )
})

Execute the following command in the command prompt:

npm test

 PASS  ./resolver.test.js
  √ Evaluate the Resolvers (471 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.628 s, estimated 2 s
Ran all test suites.

Cleanup

In the AWS CloudFormation console, delete the stack generated by the AppSync console wizard to remove all generated AWS resources.

Conclusion

Unit testing provides a mechanism for testing individual units of application code. Therefore, it’s helpful for quickly identifying and isolating issues. As part of your automated test suites, it can also be utilized to prevent bad code from being deployed into production. In this post, you learned about three approaches to test the AWS AppSync Resolvers without having to communicate with the backend data sources from your local workstation. You also learned how you can incorporate these tests as part of automated test suites. For more information on testing and debugging AWS AppSync Resolvers, refer to the AWS AppSync documentation.

Authors:

Brice Pellé

Brice Pellé is a Principal Product Manager in the AppSync team.

Venugopalan Vasudevan

Venugopalan Vasudevan is a Senior Specialist Solutions Architect focusing on AWS Front-end Web & Mobile services. Venu helps customers build their front-end and mobile strategies on AWS, including maturing and enhancing their DevOps practices.