Ace Your Tests with Mocha, Chai, Sinon, and the Clean Architecture
Tags: unittesting, integrationtesting, testing, typescript
Published: 2023-01-07 01:07:20

---
title: Ace Your Tests with Mocha, Chai, Sinon, and the Clean Architecture
published: true
date: 2023-01-06 13:59:45 UTC
tags: UnitTesting,IntegrationTesting,Testing,TypeScript
canonical_url: https://techdevblog.io/ace-your-tests-with-mocha-chai-sinon-and-the-clean-architecture/
---

 ![Ace Your Tests with Mocha, Chai, Sinon, and the Clean Architecture](https://techdevblog.io/content/images/2023/01/image_5-4.png)

Hey there! Are you ready to learn how to test a backend like a pro? Well, you're in the right place! In this tutorial, we'll be using [TypeScript](https://www.typescriptlang.org), [DynamoDB](https://aws.amazon.com/dynamodb), [Mocha](https://mochajs.org), [Chai](https://www.chaijs.com), and [Sinon.js](https://sinonjs.org) to build and test a to-do list app's backend following the [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) principles.

First things first, let's make sure you have everything you need to get started. If you don't already have them installed, you'll need to install [TypeScript](https://www.typescriptlang.org), the [AWS SDK](https://aws.amazon.com/developer/tools/), [Mocha](https://mochajs.org), [Chai](https://www.chaijs.com), and [Sinon.js](https://sinonjs.org). You'll also need to set up an [AWS](https://aws.amazon.com) account and create a [DynamoDB](https://aws.amazon.com/dynamodb) table to use as the backend for your to-do list app.

With all that out of the way, let's get started!

# Creating the backend

## Architecture

First, let's define the architecture of our to-do list app. We'll be following the [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) principles, which means that our backend will be divided into three layers: the Entities layer, the Use Cases layer, and the Adapters layer.

The Entities layer contains our core business logic and data structures. In our case, this will include a `Task` class that represents a single to-do list item, and a `TaskRepository` interface that defines the methods we'll use to interact with our [DynamoDB](https://aws.amazon.com/dynamodb) table.

The Use Cases layer contains our application-specific logic. This is where we'll define the business rules for our to-do list app, such as how to add, remove, and update tasks.

Finally, the Adapters layer contains the code that communicates with the outside world, such as our [DynamoDB](https://aws.amazon.com/dynamodb) table and any external APIs we might be using.

## Implementation

Now that we have a high-level understanding of our architecture, let's start implementing it. We'll start by defining our `Task` class in the Entities layer:

```
// src/entities/task.ts

export class Task {
  constructor(
    public readonly id: string,
    public readonly title: string,
    public readonly description: string,
    public readonly dueDate: string,
  ) {}
}

```

Next, we'll define our `TaskRepository` interface in the Entities layer:

```
// src/entities/task-repository.ts

export interface TaskRepository {
  create(task: Task): Promise;
  delete(id: string): Promise;
  find(id: string): Promise;
  list(): Promise;
  update(task: Task): Promise;
}

```

Now that we have our Entities layer set up, let's move on to the Use Cases layer. Here, we'll define a `CreateTaskUseCase` class that handles the business logic for creating a new task:

```
// src/use-cases/create-task-use-case.ts

import { Task } from '../entities/task';
import { TaskRepository } from '../entities/task-repository';

export class CreateTaskUseCase {
  constructor(private readonly taskRepository: TaskRepository) {}

  async execute(title: string, description: string, dueDate: string): Promise {
    const task = new Task(uuidv4(), title, description, dueDate);
    return this.taskRepository.create(task);
  }
}

```

As you can see, our `CreateTaskUseCase` class takes a `TaskRepository` as an argument in its constructor, and it has a single method called `execute` that creates a new `Task` object and saves it to the repository.

Now that we've implemented our Use Cases layer, let's move on to the Adapters layer. Here, we'll create a `DynamoDBTaskRepository` class that implements our `TaskRepository` interface and communicates with our [DynamoDB](https://aws.amazon.com/dynamodb) table:

```
// src/adapters/dynamodb-task-repository.ts

import { DocumentClient } from 'aws-sdk/clients/dynamodb';
import { Task } from '../entities/task';
import { TaskRepository } from '../entities/task-repository';

export class DynamoDBTaskRepository implements TaskRepository {
  constructor(
    private readonly documentClient: DocumentClient,
    private readonly tableName: string,
  ) {}

  async create(task: Task): Promise {
    await this.documentClient
      .put({
        TableName: this.tableName,
        Item: task,
      })
      .promise();
    return task;
  }

  async delete(id: string): Promise {
    await this.documentClient
      .delete({
        TableName: this.tableName,
        Key: { id },
      })
      .promise();
  }

  async find(id: string): Promise {
    const result = await this.documentClient
      .get({
        TableName: this.tableName,
        Key: { id },
      })
      .promise();
    return result.Item as Task | undefined;
  }

  async list(): Promise {
    const result = await this.documentClient
      .scan({
        TableName: this.tableName,
      })
      .promise();
    return result.Items as Task[];
  }

  async update(task: Task): Promise {
    await this.documentClient
      .update({
        TableName: this.tableName,
        Key: { id: task.id },
        UpdateExpression: 'set title = :t, description = :d, dueDate = :dd',
        ExpressionAttributeValues: {
          ':t': task.title,
          ':d': task.description,
          ':dd': task.dueDate,
        },
        ReturnValues: 'ALL_NEW',
      })
      .promise();
    return task;
  }
}

```

As you can see, our `DynamoDBTaskRepository` class uses the [AWS SDK](https://aws.amazon.com/developer/tools/)'s `DocumentClient` to communicate with our [DynamoDB](https://aws.amazon.com/dynamodb) table and implements all of the methods defined in our `TaskRepository` interface.

# Testing the backend

Now that we've implemented our backend, let's move on to testing it. We'll start by writing some unit tests for our Use Cases layer using [Mocha](https://mochajs.org) and [Chai](https://www.chaijs.com).

First, let's write a test for our `CreateTaskUseCase` class. We'll use [Sinon.js](https://sinonjs.org) to mock our `DynamoDBTaskRepository` so that we can control the behaviour of our tests:

```
// test/use-cases/create-task-use-case.test.ts

import { CreateTaskUseCase } from '../../src/use-cases/create-task-use-case';
import { DynamoDBTaskRepository } from '../../src/adapters/dynamodb-task-repository';
import { Task } from '../../src/entities/task';
import { expect } from 'chai';
import * as sinon from 'sinon';

describe('CreateTaskUseCase', function() {
  it('creates a new task and saves it to the repository', async function() {
    // Arrange
    const taskRepository = sinon.createStubInstance(DynamoDBTaskRepository);
    const createTaskUseCase = new CreateTaskUseCase(taskRepository);
    const title = 'Buy milk';
    const description = 'Remember to buy milk on the way home';
    const dueDate = '2022-01-01';
    const task = new Task('123', title, description, dueDate);
    taskRepository.create.resolves(task);

    // Act
    const result = await createTaskUseCase.execute(title, description, dueDate);

    // Assert
    expect(result).to.deep.equal(task);
    expect(taskRepository.create).to.have.been.calledOnceWithExactly(task);
  });
});

```

In this test, we use [Sinon](https://sinonjs.org)'s `createStubInstance` function to create a mock `TaskRepository` object. We then use [Sinon](https://sinonjs.org)'s `resolves` method to specify the behavior of our mock repository's `create` method. Finally, we use [Mocha](https://mochajs.org) and [Chai](https://www.chaijs.com) to define our test and make assertions about the behavior of our `CreateTaskUseCase` class.

We can follow a similar pattern to write tests for our other Use Cases and Adapters as well. For example, here's a test for our `DynamoDBTaskRepository` class:

```
// test/adapters/dynamodb-task-repository.test.ts

import { DynamoDBTaskRepository } from '../../src/adapters/dynamodb-task-repository';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';
import { Task } from '../../src/entities/task';
import { expect } from 'chai';
import * as sinon from 'sinon';

describe('DynamoDBTaskRepository', function() {
  it('creates a new task in the DynamoDB table', async function() {
    // Arrange
    const documentClient = sinon.createStubInstance(DocumentClient);
    const tableName = 'tasks';
    const taskRepository = new DynamoDBTaskRepository(documentClient, tableName);
    const task = new Task('123', 'Buy milk', 'Remember to buy milk on the way home', '2022-01-01');
    documentClient.put.resolves({});

    // Act
    await taskRepository.create(task);

    // Assert
    expect(documentClient.put).to.have.been.calledOnceWithExactly({
      TableName: tableName,
      Item: task,
    });
  });
});

```

# Conclusion

As you can see, testing our backend using [Mocha](https://mochajs.org), [Chai](https://www.chaijs.com), and [Sinon.js](https://sinonjs.org) is easy and intuitive. With just a few lines of code, we can write comprehensive tests that ensure our Use Cases and Adapters are working as expected.

Of course, this is just a small example of what these libraries can do. You can use [Mocha](https://mochajs.org), [Chai](https://www.chaijs.com), and [Sinon.js](https://sinonjs.org) to test all sorts of scenarios and edge cases, from testing error handling to testing performance. And with [TypeScript](https://www.typescriptlang.org), and [AWS DynamoDB](https://aws.amazon.com/dynamodb), you have all the tools you need to build and deploy a scalable, reliable backend for your to-do list app.

So go ahead and give it a try! And remember to delete your [DynamoDB](https://aws.amazon.com/dynamodb) table when you're done. Happy testing!

<!--kg-card-end: markdown-->