Mocking and testing
It is highly recommended that you complete the Getting Started section of Amplify setup before using local mocking.
In order to quickly test and debug without pushing all changes in your project to the cloud, Amplify supports Local Mocking and Testing for certain categories including API (AWS AppSync), Storage (Amazon DynamoDB and Amazon S3), and Functions (AWS Lambda). This includes using directives from the GraphQL Transformer, editing & debug resolvers, hot reloading, JWT mocking of authorization checks, and even performing S3 operations such as uploading and downloading content.
NOTE: Currently, on Apple Silicon Macs, Amplify sometimes fails to start mocks when using certain JDK versions built for ARM processors.
If you encounter this issue, please try using the official openJDK 16.0.1 from the Java website: JDK Download
Blog walk-through with sample app.
API mocking setup
After running amplify init
you can immediately add a GraphQL API and begin mocking without first pushing to the cloud. REST APIs are not yet supported. For example:
amplify initamplify add api # Select GraphQL, use API keyamplify mock api
When you run amplify mock api
the codegen process will run and create any required GraphQL assets such as queries, mutations, and subscriptions as well as TypeScript or Swift classes for your app. Android requires a build step for Gradle to create required classes after the codegen process completes, as well as an extra configuration in your AndroidManifest.xml.
If you do not wish to test your app locally, you can still use the local GraphQL console as well as edit, debug, and test your VTL resolvers locally against the mock endpoint.
When adding a schema use an API Key at first to ensure everything works, though you can authenticate against a Cognito User Pool and the local testing server will honor the JWT tokens. You can also mock the JWT tokens in the local console (outlined below), however in that case you will need to do an amplify push
first to create the User Pool.
When defining a schema you can use directives from the GraphQL Transformer in local testing as well as local code generation from the schema for types. The following directives are currently supported in local testing:
Mocking the Lambda triggers on @model
types
If you have DynamoDB Lambda triggers set up on @model
types in your GraphQL schema, by following steps listed here, then you can test those Lambda triggers locally via amplify mock
or amplify mock api
.
The connected Lambda triggers are automatically invoked locally if you perform a CRUD operation on the @model
type using either the local DynamoDB endpoint (http://localhost:62224
) or the local AppSync console (should be http://localhost:20002
). The structure of the event that is sent to the lambda trigger can be found here.
The environment variables that are listed below in Function mock environment variables, are available for each invocation of the Lambda trigger.
In addition, you can use a .env
file within the function directory (ie. <project root>/amplify/backend/function/<function name>/.env
) to override any environment variables for local mocking.
Mocking @model
types with @searchable
If you use the @searchable
directive on @model
types in your GraphQL schema, then you can test the search GraphQL queries that are generated for you locally via amplify mock
or amplify mock api
. To learn more about the search queries, refer to this guide.
We create the following artifacts when you mock the API with searchable models:
- OpenSearch version
1.3.0
is downloaded. - A Python Lambda trigger is stored locally at
mock-api-resources/searchable/searchable-lambda-trigger
. This Lambda trigger is automatically invoked locally if you perform a CRUD operation on the searchable model types. It updates the data in the corresponding search index used by the local OpenSearch cluster. - A data folder to store the OpenSearch indices is created at
mock-api-resources/searchable/searchable-data
.
Note: IAM authorization rules in Mock get are treated as Auth role if the request is signed with AccessKey
ASIAVJKIAM-AuthRole
. Otherwise the request is treated as made by an unAuth user.
Storage mocking setup
For S3 storage mocking, after running amplify init
you must first run through amplify add auth
, either explicitly or implicitly if adding storage first, and then run an amplify push
. This is because mocking storage in client libraries requires credentials for initial setup. Note however that S3 authorization rules, such as those placed on a bucket policy, are not checked by local mocking at this time.
Once you have done an initial push you can run the mock server and hit the local endpoint:
amplify initamplify add storage # This will prompt you to add authamplify pushamplify mock storage
To use an iOS application with the local S3 endpoint you will need to modify your Info.plist file. To use an Android application with the local S3 endpoint you will need an extra configuration in your AndroidManifest.xml.
For DynamoDB storage, setup is automatically done when creating a GraphQL API with no action is needed on your part. Resources for the mocked data, such as the DynamoDB Local database or objects uploaded using the local S3 endpoint, inside your project under amplify/mock-data
.
Function mocking setup
After adding a function to your project with amplify add function
you can test it using amplify mock function
.
amplify mock function
supports the following arguments:
<function name>
- The name of the function to mock. Must immediately followamplify mock function
, eg.amplify mock function myFunctionName
--event "<path to event JSON file>"
- Use the specified JSON file as the event to pass to the Lambda handler--timeout <number of seconds>
- Override the default 10 second function response timeout with a custom timeout value
Additionally, running amplify mock function
allows selecting and testing multiple functions.
Function mocking with GraphQL
A GraphQL Lambda resolver connected to your schema using @function can be mocked using amplify mock api
. For example, if you have a function named quoteOfTheDay and a schema like:
type Query { getQuote: String @function(name: "quoteOfTheDay-${env}")}
Then when running amplify mock api
, the local GraphQL endpoint will invoke this function locally when running a GraphQL query such as:
query { getQuote}
Function mock environment variables
amplify mock function
populates environment variables that mimic what will be present when deployed in the cloud. Amplify parses the function's CloudFormation template and attempts to resolve any environment variables specified there (also review function mock limitations).
CloudFormation parameters will be resolved by:
- Resolving values specified in the
parameters.json
file for the function - Resolving values specified in
team-provider-info.json
within the<env>.categories.function.<function name>
block, where<env>
is the currently checked out environment and<function name>
is the function being mocked AWS::Region
,AWS::AccountID
,AWS::StackName
, andAWS::StackId
are resolved by parsing theawscloudformation
configuration of the current environment inteam-provider-info.json
- Parameters constructed from dependencies on other resources in the project are resolved by parsing
amplify-meta.json
. Additionally, if a mock API is currently running and the function depends on the API, the local API URL will replace the cloud URL
The mock environment will also populate lambda runtime environment variables in the following way:
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
, andAWS_SESSION_TOKEN
will be populated using the AWS credentials that the Amplify project is currently configured to use_HANDLER
,AWS_REGION
,AWS_LAMBDA_FUNCTION_NAME
,LAMBDA_TASK_ROOT
, andLAMBDA_RUNTIME_DIR
will be set based on the function being mocked- Static defaults will be specified for all other runtime environment variables. The full list of static defaults can be found here
You can also override any mock environment variables in a .env
file within the function directory (ie. <project root>/amplify/backend/function/<function name>/.env
).
Connecting to a mock @model
table
Connect a mock function that operates on a table generated by @model
to the mock table when running amplify mock api
by:
- Create a
.env
file in the function directory with the following:
AWS_REGION=us-fake-1DDB_ENDPOINT=http://localhost:62224AWS_ACCESS_KEY_ID=fakeAWS_SECRET_ACCESS_KEY=fakeAPI_<api name>_<model>TABLE_NAME=<model>Table
Replace <api name>
with the name of your API and <model>
with the name of your model. The environment variable name should be all capitalized. For example, if you have an API named "FlightStats" and a model defined as type Airplane @model {...}
, then then last line of the .env
file should be:
API_FLIGHTSTATS_AIRPLANETABLE_NAME=AirplaneTable
- Configure the DynamoDB client in your function as follows:
const ddb = new aws.DynamoDB.DocumentClient({ endpoint: process.env.DDB_ENDPOINT});const result = await ddb .put({ TableName: process.env.API_FLIGHTSTATS_AIRPLANETABLE_NAME, Item: { id: '1234567890', name: 'F-22', description: 'Cool fighter jet' } }) .promise();
- Run
amplify mock api
to activate the local DynamoDB endpoint - Run
amplify mock function
which will now connect to the mock DynamoDB table
When running in the cloud, these environment variables will have valid values except DDB_ENDPOINT
which will be undefined
. In this case the DynamoDB client will use the default endpoint.
When running locally, these environment variables will be populated using the local .env file which specifies the fake values required to connect to the local database.
Note: While connected to the mock database, calls to other AWS resources within the mock function will not work because the AWS credential environment variables have been overwritten with fake credentials. To call a mock table as well as other AWS services, add logic to switch between the fake credentials and the real ones in the DynamoDB client configuration. In this case, you could configure your .env
file with:
IS_MOCK=true
And configure the DynamoDB client with:
const clientConfig = process.env.IS_MOCK ? { region: 'us-fake-1', endpoint: 'http://localhost:62224', credentials: new aws.Credentials({ accessKeyId: 'fake', secretAccessKey: 'fake' }) } : undefined;const ddb = new aws.DynamoDB.DocumentClient(clientConfig);
Function mock limitations
amplify mock function
does not attempt to fully simulate the Lambda runtime environment. There may be some cases where the behavior of your function when mocking differs from executing in the cloud.
For example, mock runs on your local OS and does not attempt to emulate Amazon Linux which executes your function in the cloud. Testing with amplify mock function
should be used to get quick feedback on the correctness of your function but should not be used as a substitute for testing in a cloud development environment.
Mocking multiple resources
For mocking API, Storage, or Functions together, run the following:
amplify mock
The mock should display the following prompt, depending on the mockable resources added to the project:
? Select the category … (Use arrow keys or type to filter)❯● GraphQL API ○ Function ○ Storage
Config files
When performing operations against the local mock endpoint, the Amplify CLI will automatically update your aws-exports.js
and awsconfiguration.json
with the local endpoints, fake values where necessary (e.g. fake API key), and disable SSL with an explicit value (DangerouslyConnectToHTTPEndpointForTesting
) to indicate the functionality is only for local mocking and testing. This happens automatically when you run amplify mock
and the server is running. Once you stop the mock server the config files are updated with the correct cloud endpoints for your project and DangerouslyConnectToHTTPEndpointForTesting
is removed from the config file.
aws-exports.js example
const awsmobile = { aws_project_region: 'us-east-1', aws_appsync_graphqlEndpoint: 'http://localhost:20002/graphql', aws_appsync_region: 'us-east-1', aws_appsync_authenticationType: 'AMAZON_COGNITO_USER_POOLS', aws_appsync_apiKey: 'da2-fakeApiId123456', aws_appsync_dangerously_connect_to_http_endpoint_for_testing: true, aws_cognito_identity_pool_id: 'us-east-1:270445b2-cc92-4d46-a937-e41e49bdb892', aws_cognito_region: 'us-east-1', aws_user_pools_id: 'us-east-1_excPT39ZN', aws_user_pools_web_client_id: '4a950rsq08d2gi68ogdt7sjqub', oauth: {}, aws_user_files_s3_bucket: 'local-testing-app-2fbf0a32d1896419b88f004c2755d084c-dev', aws_user_files_s3_bucket_region: 'us-east-1', aws_user_files_s3_dangerously_connect_to_http_endpoint_for_testing: true};
awsconfiguration.json example
"AppSync": { "Default": { "ApiUrl": "http://localhost:20002/graphql", "Region": "us-east-1", "AuthMode": "AMAZON_COGNITO_USER_POOLS", "ClientDatabasePrefix": "deddd_AMAZON_COGNITO_USER_POOLS", "DangerouslyConnectToHTTPEndpointForTesting": true }},"S3TransferUtility": { "Default": { "Bucket": "local-testing-app-2fbf0a32d1896419b88f004c2755d084c-dev", "Region": "us-east-1", "DangerouslyConnectToHTTPEndpointForTesting": true }}
iOS config
When running against the local mock S3 server with iOS you must update your Info.plist
to not require SSL when on a local network. To enable this set NSAllowsLocalNetworking
to YES
under NSAppTransportSecurity
. This will scope the security exception to only run on localhost domains as outlined in Apple Developer documentation for NSAllowsLocalNetworking.
Android config
When running against the local mock server with Android it is recommended to use additional Build Variants, such as a Debug and Release, to enable cleartext traffic only if the app is running on your local network. This will help ensure that you do not allow unsecured HTTP traffic in your Release Build Variant.
For example, in your Android Studio project create /src/debug/AndroidManifest.xml
and in this file create a network configuration file reference android:networkSecurityConfig="@xml/network_security_config"
:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application android:networkSecurityConfig="@xml/network_security_config" /></manifest>
Then create the network configuration file /src/debug/res/xml/network_security_config.xml
and restrict to only run on your localhost IP range:
<?xml version="1.0" encoding="utf-8"?><network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">10.0.2.2</domain> </domain-config></network-security-config>
Then use a Build Variant and run the Debug build and only test this setting with your local mock server. To learn more about this please see the official Android documentation.
Alternatively, if you are running a non-production application and do not want to use multiple Build Variants, you can set android:usesClearTextTraffic="true"
in your AndroidManifest.xml as in the code snippet below. This is not a recommended practice. Ensure you remove this once mocking is complete.
<application android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" android:usesClearTextTraffic="true" >
<!--other code--></application>
GraphQL Local Console
To start testing, before starting your JavaScript/Android/iOS application run the following command:
amplify mock
Alternatively, you can run amplify mock api
to only mock the API category. When prompted, ensure you select YES to automatically generate queries, mutations, and subscriptions if you are building a client application.
Once the server starts it will print a URL. Open this URL in your browser (it should be http://localhost:20002
) and the OneGraph GraphQL console will open up in your browser. You can use the explorer on the left to build out a query/mutation or manually type your statements in the main window. Amplify mocking will use DynamoDB Local to persist the records on your system. If you wish, you can view these in Visual Studio code with SQLite Explorer. Follow the instructions in that repo for connecting to local databases.
When your API is configured to use Cognito User Pools, the local console provides a way to change Username
, Groups
, and email
of the bundled JWT token. These values are used by GraphQL transformers Auth directive. Edit them by clicking Auth and saving your changes, then run operations in the console to test your rules.
GraphQL Resolver Debugging
You can edit VTL templates locally to see if they contain errors, including the line numbers causing problems, before pushing to AppSync. With the local API running navigate to amplify/backend/api/APINAME/resolvers
where APINAME
is the logical name that you used when running $amplify add api
. You will see a list of resolver templates that the Transformer generated. Modify any of them and save, and they will be immediately loaded into the locally running API service with a message Mapping template change detected. Reloading.
. If there is an error you will see something such as the following:
Reloading failed Error: Parse error on line 1:...son($context.result----------------------^
If you stop the server locally, for instance to push your changes to the cloud, all of the templates in the ../APINAME/resolvers
directory will be removed except for any that you modified. When you subsequently push to the cloud these local changes will be merged with your AppSync API.
Modify schema and test again
As you are developing your app, you can always modify the GraphQL schema which lives in amplify/backend/api/APINAME/schema.graphql
. You can modify any types using any of the supported directives and save this file, while the local server is still running. The changes will be detected and if your schema is valid they will be hot reloaded into the local API. If there is an error in the schema an error will be printed to the terminal like so:
Unknown directive "mode".
GraphQL request (1:11)1: type Todo @mode{ ^2: id: ID!
at GraphQLTransform.transform
Amplify libraries when configured for these categories can use the local mocked endpoints for testing your application. When a mock endpoint is running the CLI will update your aws-exports.js
or awsconfiguration.json
to use the mock server and once stopped they will be updated to use the cloud endpoint once you have run an amplify push
.