DEV Community

Cover image for Building Mocks-First Automated Testing with Cypress and AWS DynamoDB
Sehani Chathurangi for Cypress

Posted on

Building Mocks-First Automated Testing with Cypress and AWS DynamoDB

When testing applications that depend on cloud services, manually preparing database records for every test can quickly become time-consuming, repetitive, and error-prone.

A better approach is to automate test-data creation using reusable fixtures, custom Cypress tasks, and the AWS SDK.

In this article, I'll walk through how I built a mocks-first testing approach using Cypress and AWS DynamoDB, where each test can create the data it needs, execute independently, and verify the resulting database state.

Why Mocks-First Testing?

A mocks-first approach allows tests to control their required data and dependencies instead of relying on manually prepared records.

Rather than creating database records before running a test, the test itself prepares the required state during execution.

This provides several benefits:

  • Faster test execution
  • Repeatable test scenarios
  • Reusable test data
  • Less manual setup
  • Better test isolation
  • Easier maintenance
  • More reliable CI/CD execution

The goal is not to mock every AWS service. Instead, the approach focuses on controlling test data and external dependencies so that tests remain predictable and repeatable.


The Overall Approach

The architecture can be summarized as:

Cypress Test → cy.task() → Node.js → AWS SDK → DynamoDB

The Cypress test is responsible for describing the scenario and assertions, while the Node.js layer handles AWS-specific operations.

This separation keeps the test code clean and prevents AWS database logic from being duplicated across test cases.


Step 1: Create Reusable Mock Data

The first step is to create reusable JSON fixtures.

cypress/
└── fixtures/
    └── test-data.json
Enter fullscreen mode Exit fullscreen mode

The fixture contains the information required to create the database record.

Instead of hardcoding database values inside every test, the fixture becomes the base payload that can be reused across multiple scenarios.

For example:

{
  "id": "test-12345",
  "name": "Sample Test Data",
  "category": "TEST",
  "status": "ACTIVE"
}
Enter fullscreen mode Exit fullscreen mode

Different fixtures can also be created for:

  • Positive scenarios
  • Negative scenarios
  • Boundary conditions
  • Error scenarios
  • Different transaction states

This keeps test data separate from test logic.


Step 2: Separate Database Logic

The next step is to keep DynamoDB operations outside the Cypress test itself.

A dedicated helper can be responsible for:

  • Creating the DynamoDB client
  • Converting fixture data into database records
  • Generating partition keys and sort keys
  • Calculating TTL values
  • Inserting records
  • Retrieving records
  • Deleting test data when required

For example, a helper might expose operations such as:

insertMockData()
getMockData()
deleteMockData()
Enter fullscreen mode Exit fullscreen mode

The important principle is separation of concerns.

The test should describe what data it needs, while the helper handles how that data is stored in DynamoDB.


Step 3: Expose Database Operations Through Cypress Tasks

Cypress provides cy.task() as a bridge between the Cypress test environment and Node.js.

Instead of putting AWS SDK operations directly inside the browser-side test, the test can call a custom task:

cy.task('insertMockData', mockData);
Enter fullscreen mode Exit fullscreen mode

The flow becomes:

Cypress Test
     │
     ▼
 cy.task()
     │
     ▼
 Node.js
     │
     ▼
 AWS SDK
     │
     ▼
 DynamoDB
Enter fullscreen mode Exit fullscreen mode

Typical Cypress tasks can include:

  • insertMockData
  • getMockData
  • deleteMockData

This approach centralizes AWS operations and makes them reusable across the entire test suite.


Step 4: Configure Environment Variables

Cloud configuration should never be hardcoded inside test files.

Environment-specific values can be provided through configuration such as:

  • AWS Region
  • AWS credentials
  • DynamoDB table name
  • Local DynamoDB endpoint
  • Other environment-specific settings

This allows the same test suite to run against different environments.

For example:

Local
  ↓
Development
  ↓
QA
  ↓
CI/CD
Enter fullscreen mode Exit fullscreen mode

The test logic remains unchanged while the configuration changes according to the execution environment.

Credentials and secrets should always be managed securely and should never be committed to the repository.


Step 5: Build Flexible Test Data

Creating a separate fixture for every test scenario can eventually lead to duplicated test data.

Instead, a base fixture can be loaded and overridden only where necessary.

For example:

const testData = {
  ...baseFixture,
  id: `test-${Date.now()}`,
  status: 'INACTIVE'
};
Enter fullscreen mode Exit fullscreen mode

The base fixture provides the common structure, while the test customizes only the values relevant to the scenario.

This approach makes it easier to generate unique test records while keeping the fixture maintainable.

It is especially useful when testing:

  • Multiple transactions
  • Different statuses
  • Different merchants
  • Edge cases
  • Concurrent processing
  • Large datasets

Step 6: Verify the Database State

Creating test data is only part of the process.

After the application processes the data, the test should retrieve the relevant DynamoDB record and validate the resulting state.

Typical validations include:

  • Record exists
  • Partition key is correct
  • Sort key is correct
  • Required attributes are stored
  • Status is updated correctly
  • Configuration values match expectations
  • TTL is generated correctly
  • Expected processing results are persisted

For example:

Insert test data
       ↓
Application processes data
       ↓
Retrieve DynamoDB record
       ↓
Validate final state
Enter fullscreen mode Exit fullscreen mode

This allows the test to validate not only that data can be written, but also that the application's workflow produces the expected result.


Why This Structure Works

The approach separates the test suite into three main responsibilities:

Fixtures
   ↓
Test Data

Database Helpers
   ↓
AWS / DynamoDB Operations

Cypress Tests
   ↓
Business Scenarios + Assertions
Enter fullscreen mode Exit fullscreen mode

This separation provides a cleaner architecture and makes individual components easier to maintain.

If the DynamoDB implementation changes, the test cases do not necessarily need to change.

If the test scenario changes, the AWS helper does not need to change.


Moving Toward Event-Driven Testing

Directly inserting records into DynamoDB is useful for preparing controlled test states, but it can also bypass parts of the application's normal workflow.

This approach can therefore become the foundation for a more complete event-driven testing strategy.

The current approach might look like:

Fixture
   ↓
Cypress Task
   ↓
DynamoDB
   ↓
Application / Verification
Enter fullscreen mode Exit fullscreen mode

The next evolution could be:

Fixture
   ↓
Event / SNS
   ↓
Application
   ↓
DynamoDB
   ↓
Cypress Verification
Enter fullscreen mode Exit fullscreen mode

Instead of directly creating the database record, the same mock payload can be published as an event.

The application then processes the event through its normal workflow, and Cypress verifies the final database state.

The biggest advantage is that the test-data model can remain reusable.

The injection point changes, but the underlying fixture structure can remain largely the same.


When This Approach Is Useful

This pattern works particularly well for:

  • Integration testing
  • Event-driven applications
  • DynamoDB-backed services
  • CI/CD pipelines
  • Repeatable test scenarios
  • Large-scale automated test suites
  • Tests requiring controlled database state

However, direct database seeding should be used carefully for end-to-end testing because it can bypass application logic that you may actually want to validate.

A good testing strategy can use both approaches:

Database seeding for controlled setup and targeted integration tests.

Event/API-driven setup for validating the application's complete workflow.


Key Benefits

Using reusable fixtures together with Cypress tasks provides several advantages:

  • Cleaner test code — AWS logic stays outside the test cases.
  • Reusable mock data — Fixtures can support many scenarios.
  • Reduced duplication — Base data can be overridden when needed.
  • Faster execution — Tests do not depend on manual database preparation.
  • Better test isolation — Each test can create its own required state.
  • Centralized AWS logic — DynamoDB operations are maintained in one place.
  • CI/CD friendly — Test data can be generated automatically.
  • Easy evolution — The same data model can support future event-driven testing.

Final Thoughts

Automating database setup with reusable fixtures, Cypress tasks, and the AWS SDK can remove a significant amount of repetitive manual work from integration testing.

The key is to keep test data, database operations, and test scenarios separate.

Instead of manually preparing DynamoDB records before every test, the test suite becomes responsible for creating the state it needs and validating the resulting behavior.

More importantly, this approach creates a foundation for moving toward event-driven testing. As the application architecture evolves, the same reusable test data can be injected through events and processed through the application's normal workflow.

The result is a testing architecture that is repeatable, maintainable, scalable, and easier to integrate into CI/CD pipelines.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.