Logo

dev-resources.site

for different kinds of informations.

Testing AppSync Subscriptions

Published at
1/9/2025
Categories
aws
serverless
appsync
ownership
Author
setho
Categories
4 categories in total
aws
open
serverless
open
appsync
open
ownership
open
Author
5 person written this
setho
open
Testing AppSync Subscriptions

AppSync has three types of GQL operations: Queries, Mutations, and Subscriptions. These operations can be divided into two broad categories: Synchronous and Asynchronous.

Queries and Mutations are straightforward to implement and test due to their synchronous nature. Subscriptions are more complicated, both in how they are put together and how they are validated. Today, I want to focus on that validation: how to test AppSync subscriptions.

Subscription Complexity

What makes GQL subscriptions so complex? First, they are tied to a mutation. This mutation is often implemented as an internal-only, "dummy"1 mutation to guarantee the projected properties. See Tamas Sallai's excellent article on why this is so, linked below in References.

Now, we want to test this complex thing we just built. Subscriptions work off of asynchronous events. I have previously written2 about whether you should test your application's eventing. Sometimes, it's not worth the effort. However, with GQL subscriptions, I have much more to test than a mere call to EventBridge. There is too much that can go wrong to forego these tests.

The last thing that makes testing GQL subscriptions difficult is the tooling. I experienced issues combining multiple subscriptions per Jest test fixture. Plus, the teardown can take many seconds for each fixture as the connections clean themselves upon termination. I expect this to improve over time as the tooling and techniques improve.

Our Example

Let's assume that our user, Jack, uses a popular e-commerce site and wants to be alerted if one of his favorite stores, Acme, adds a new product. Here is a diagram of the basic flow.

Basic Flow of The System

  1. The products service emits a "New Product Added to Acme" event.
  2. This event triggers a Lambda in our AppSync project, listening for new product events.
  3. This Lambda invokes a "dummy" mutation, notifyProductAdded
  4. The dummy mutation triggers the subscription
  5. Jack receives his notification

There is a lot that can go wrong. We need tests.

Choosing Your Testing Width

Let's examine two subscription testing approaches: "wide" and "narrow."

Wide testing points
The wide test involves both the resource service and AppSync. In this case, we call the resource service with an operation, "Create a Product," and let the event flow through AppSync.

Narrow testing points
The narrow approach stays within the AppSync application and begins by directly invoking the event-handling Lambda. In both cases, we spin up a GQL subscription before starting the test and validating the response.

Let's examine some of the techniques for setting up tests like this. Later, we'll consider why you might choose "wide" over "narrow" or vice versa.

Subscription Testing Techniques

I will be testing in NodeJS using the Jest framework. Many GQL libraries in NPM have great support for subscriptions. However, almost all are intended to work in a Web browser, not NodeJS. I had some success using the aws-amplify package. I handle the entire subscription setup using one method: setUpSubscription. Let's look at the key sections of this method.

Before you go any further, you must import and require WS, a Node.js WebSocket library. Amplify is also intended for a browser and expects to use the browser-native WebSocket object. To test in NodeJS, you must include this line.

Importing WS

The first thing I do is to configure Amplify for my application. In this case, I am using an API key to authorize the call to subscribe.

Amplify configuration

The next section spins up a Hub listener, which emits events about the connection itself. I use this to ensure I am fully connected before triggering the dummy mutation. The setup returns a function I call stopHubListener to cleanly shut down the Hub after the test completes.

Hub listener

Next, I set up the subscription itself. This portion is straightforward and looks like any GQL invocation. Note that setting up the next() handler will add a result to the array the caller passed in, validating that a message was received through the subscription.

Amplify client

Finally, I wait until my Hub listener is ready. When it is safe to exercise the subscription, it will set its connection state to "Connected."

Await connection state

The examples I used above are in a working application you can review at this GitHub repository. Please see the source code for more details than I provided today.

From here, the test is easy. Depending on your starting point, wide or narrow, you invoke the resource service or the Lambda, respectively. Then, you wait for the expected message to appear in your results array.

A Note of Caution

I had difficulties combining multiple subscription tests into the same test fixture. I don't know whether this was a Jest/Amplify limitation or my limited knowledge. Breaking them into many fixtures (files in Jest) works fine for me, as I run fixtures in parallel with my test runners.

When you have problems, especially lingering WebSocket connections, your tests will hang after passing or failing. These issues are difficult to unwind. Go slow. Build things in small chunks, and keep your test suite healthy as you go.

Other Auth Mechanisms

The example above uses an API key as its primary authorization. What if you don't use an API key? The Amplify library has you covered. It supports all the auth schemes that AppSync does, including custom (Lambda) authorization.

To support custom/Lambda authorization for your subscription, you must make two small changes to your subscription setup. The first is to simplify the call to Amplify.configure() and only pass in the endpoint and region.

Amplify configuration with custom auth

The other change is to the call to subscribe. Here, you pass in the authorization mode and token.

Amplify client with custom auth

The remainder of the method should look identical to our API key version.

Wide or Narrow?

Where should your tests begin, at the resource service (wide) or with your event handler (narrow)? There are pros and cons to each approach.

A wide test tells you the most information. However, it couples your test to the resource service. If that service is experiencing problems, a wide test will fail and block your deployment pipeline.

A narrow test can be implemented ahead of time and independently from the resource service. It provides information about your AppSync API but not its interaction with other resource services.

The fundamental guideline is that your testing width should follow your deployment topology. Is the AppSync API part of the resource service’s deployment? Use wide tests. Is it an independently deployed unit? Use narrow tests. Keeping your deployment pipelines independent is crucial to managing a microservices architecture. Watch out for unnecessary coupling between projects.

Summary

AppSync subscriptions can be complicated. In the projects I've built, they were the most complex endpoints to set up. Knowing they work as expected goes a long way to building confidence that your AppSync API is solid. I hope this post helps you build that confidence.

Happy building!

References

Ownership Matters: Testing EventBridge with Serverless
GitHub: Serverless Reference Architectures appsync-to-http
Tamas Sallai: Real-time data with AppSync subscriptions
Ownership Matters: Testing AppSync JavaScript Resolvers


  1. I first heard Yan Cui use this term, but I don't know if it originated with him. ↩

  2. Testing EventBridge with Serverless ↩

serverless Article's
30 articles in total
Favicon
Detect Inappropriate Content with AWS Rekognition
Favicon
How can I track my order in Maruti courier?
Favicon
Getting Started with AWS Lambda: A Guide to Serverless Computing for Beginners
Favicon
Deploying Flutter Web Apps using Globe.dev
Favicon
Unlocking the Power of AWS API Gateway and AWS AppSync: Transforming API Development, Functionality, and Use Cases
Favicon
Back to MonDEV 2025
Favicon
Building a Twitter OAuth Authentication Header Generator with Vercel Serverless Functions
Favicon
The Role of Serverless Architecture in Modern Website Development: Benefits and Impact
Favicon
This Serverless Function has crashed. Your connection is working correctly. Vercel is working correctly.
Favicon
Guide to modern app-hosting without servers on Google Cloud
Favicon
Testing AppSync Subscriptions
Favicon
Amazon SES Unwrapped: Key Lessons & Testing Tips for Building Robust Email Systems
Favicon
Spring Boot 3 application on AWS Lambda - Part 14 Measuring cold and warm starts with GraalVM Native Image and memory settings
Favicon
Deploy an Express.js API on Vercel πŸš€
Favicon
Serverless for greenfield projects: How data-driven architectures are revolutionizing your software development
Favicon
2024 in Review: Key Highlights in Cloud Databases
Favicon
Managing Secrets in an AWS Serverless Application
Favicon
Build a highly scalable Serverless CRUD Microservice with AWS Lambda and the Serverless Framework
Favicon
[Boost]
Favicon
Becoming an AWS (Serverless) Hero
Favicon
Creating an AWS + NextJS site for the Cloud Resume Challenge
Favicon
Building Serverless APIS with Serverless,Node.js, Typescript, DynamoDB, and Lambda.
Favicon
Highly scalable image storage solution with AWS Serverless at ip.labs - Part 3 Building File API for Uploads and Downloads
Favicon
Serverless self-service IoT certificate management - Part 2
Favicon
How to return meaningful error messages with Zod, Lambda and API Gateway in AWS CDK
Favicon
AWS CDK - aws-lambda-nodejs Module (updated)
Favicon
nestJS Modern Framework for Scalable Applications
Favicon
Serverless OAuth2/OIDC server with OpenIddict 6 and RDS Aurora v2
Favicon
Why Your System Slows Under Load (And What to Do About It)
Favicon
HubSpot offers a powerful platform for creating user interfaces (UI) and serverless functions using React, allowing users to develop highly customizable pages and forms directly within the CRM itself.

Featured ones: