Building RESTful Web APIs with Dart, Aqueduct, and PostgreSQL — Part 4: Testing
Tags: api, webdev, dart, testing
Published: 2018-05-31 08:21:36

---
title: Building RESTful Web APIs with Dart, Aqueduct, and PostgreSQL — Part 4: Testing
published: true
description: Learn how to build your own RESTful Web APIs with Dart, Aqueduct, and PostgreSQL
tags: api, webdev, dart, testing
series: Building RESTful Web APIs with Dart, Aqueduct, and PostgreSQL
---

![Featured image for Building RESTful Web APIs with Dart, Aqueduct, and PostgreSQL](https://thepracticaldev.s3.amazonaws.com/i/dv19znif0x9wpvdcdt13.jpg)

---

_**PLEASE NOTE: As of Dart 2 the API for Aqueduct has changed, leading to breaking changes. This article was based on Aqueduct 2.5.0 for Dart v1.**_

_**I have updated this as a new video series: [http://bit.ly/aqueduct-tutorial](http://bit.ly/aqueduct-tutorial)**_

---

In Part 3 we integrated our API with a PostgreSQL database, utilising Aqueduct's ORM as a means of managing our data transactions. Having learnt about managed objects and managed contexts, we landed a solution that provided data persistence without the need of writing complex SQL queries.

This article is part of a series, covering these topics:

* [Part 1: Setting up and running the example](https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresql-55k)
* [Part 2: Implementing routing with CRUD operations](https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresqlpart-2-routing-53ip)
* [Part 3: Connecting Web APIs to PostgreSQL database](https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresqlpart-3-postgres-9c7)
* **Part 4: Writing tests** (we're here)
* [**\*Bonus content\*** DB Migration and Model Relationships](https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresqlbonus-content-551g) 😄

In this part we'll be writing our tests while refactoring our logic to accommodate these tests. We will be using Aqueduct's inbuilt testing library built atop the Dart team's [test](https://pub.dartlang.org/packages/test) package, and we are saved the hustle of setting this up ourselves.

---

## The test harness

Using the scaffolding tool in Part 1 created a `test/` folder at the project root, with the file structure below:

```
test/
|--harness
   |--app.dart
   example_test.dart
```
The test harness `harness/app.dart` is responsible for starting and stopping our application. We see this in effect when looking at this snippet in `test/example_test.dart`:

```dart
TestApplication app = new TestApplication();

// Runs before all tests
setUpAll(() async {
  await app.start();
});

// Runs after all tests
tearDownAll(() async {
  await app.stop();
});
```
The application is started before running all our tests and stopped immediately afterwards. Our test harness replicates `bin/main.dart` with these exceptions:

1. A port `0` is specified so that our tests can run on any available port
2. A separate configuration file(config.src.yaml) is given containing test-specific data
3. The `runOnMainIsolate` option is set to true when `application.start` is called, running our test on a main thread. This disables multi-threading so that we can access the application's state and services to perform our assertions.
4. A `TestClient` is instantiated to provide a HTTP client for performing requests to our APIs. Using this will give us test responses for making our assertions on.
5. A method for stopping the application is provided to be invoked after all tests are run

In order to run our tests, let's refactor our solution in Part 3, starting with our database configuration. This is to allow flexibility to support testing and production environments.

## 1. Configure the database

Looking again at `FaveReadsSink` in `lib/fave_reads_sink.dart`, the same Postgres details will be used in our test and production environments (*Yikes!*)

The use of configuration files help mitigate this by separating test and production information. In our project we will be working with `config.src.yaml` and `config.yaml` files at the root level.

Let's start by amending these files with our db connection information:

```yaml
# config.src.yaml - for test environment
database:
  username: dartuser
  password: dbpass123
  host: localhost
  port: 5432
  databaseName: fave_reads_test
  isTemporary: true
```
And in our config.yaml file:

```yaml
# for production environment
database:
  username: dartuser
  password: dbpass123
  host: localhost
  port: 5432
  databaseName: fave_reads
  isTemporary: false
```
This allows us to make the following modifications to `FaveReadsSink`:

```dart
// fave_reads_sink.dart
// ...
// ...
class FaveReadsSink extends RequestSink {
  FaveReadsConfiguration config;

  FaveReadsSink(ApplicationConfiguration appConfig) : super(appConfig) {
    logger.onRecord.listen(
        (rec) => print("$rec ${rec.error ?? ""} ${rec.stackTrace ?? ""}"));

    var configFilePath = appConfig.configurationFilePath;
    config = new FaveReadsConfiguration(configFilePath);

    var managedDataModel = new ManagedDataModel.fromCurrentMirrorSystem();
    var persistentStore = new PostgreSQLPersistentStore.fromConnectionInfo(
        config.database.username,
        config.database.password,
        config.database.host,
        config.database.port,
        config.database.databaseName);

    ManagedContext.defaultContext =
        new ManagedContext(managedDataModel, persistentStore);
  }
  // ...
  // ...
}
```
Our configuration file path is specified by the `configurationFilePath` property on the `Application` constructor in `bin/main.dart` and `test/harness/app.dart`. Our `FaveReadsSink` class is instantiated inside an application object, via which it receives the configuration path in the `appConfig` argument of our request sink constructor.

We then extract our configuration information by instantiating `FaveReadsConfiguration`, a subclass of the `ConfigurationItem` helper class. This parses the configuration file as a Map.

After the request sink, let's define our configuration item:

```dart
class FaveReadsConfiguration extends ConfigurationItem {
  FaveReadsConfiguration(String fileName) : super.fromFile(fileName);

  DatabaseConnectionConfiguration database;
}
```
The `database` property maps to the same key in our configuration file. This is what exposes our database information to be accessed like such: `config.database.username`

## 2. Extract SchemaBuilder into utility file

Let's move the `createDatabaseSchema` method into a separate file to also be used by our test harness:

```dart
// This goes in lib/utils/utils.dart
import 'dart:async';
import 'package:aqueduct/aqueduct.dart';

Future createDatabaseSchema(ManagedContext context, bool isTemporary) async {
  try {
    var builder = new SchemaBuilder.toSchema(
        context.persistentStore,
        new Schema.fromDataModel(context.dataModel),
        isTemporary: isTemporary);

    for (var cmd in builder.commands) {
      await context.persistentStore.execute(cmd);
    }
  } catch (e) {
    // Database may already exist
  }
}
```
We now have the second parameter `isTemporary` to be set by our configuration files. This option determines whether our data is persisted or not. We set this to `true` for our tests.

Let's now import this utility back into `lib/fave_reads_sink.dart`:

```dart
import 'fave_reads.dart';
import './controller/books_controller.dart';
import './utils/utils.dart'; // 👈👈👈

class FaveReadsSink extends RequestSink {
  //...
  //...
  @override
  Future willOpen() async {
    await createDatabaseSchema(
        ManagedContext.defaultContext, config.database.isTemporary);
  }
  //...
}
//...
```
## 3. Set up the testing database

Open the `psql` tool and run the query below:

```sql
CREATE DATABASE fave_reads_test;
CREATE USER dartuser;
ALTER USER dartuser WITH password 'dbpass123';
GRANT ALL ON database fave_reads_test TO dartuser;
```
You can skip lines 2 and 3 if you already did this in Part 3.

## 4. Write your tests

Rename `example_test.dart` to `books_controller_test.dart` and replace its contents with the below:

{% gist https://gist.github.com/graphicbeacon/a77bee5bbb56e596efd347601b01adb8 %}

On line 39 we call the `discardPersistentData` method in order to disconnect and reconnect the database, using the same setup data for each test we run. Since our test data is temporary, it only lasts for the duration of the connection.

We still need to create this method in our test harness:

```dart
// test/harness/app.dart
class TestApplication {
  // ...
  Future discardPersistentData() async {
    await ManagedContext.defaultContext
      .persistentStore.close();
    await createDatabaseSchema(
      ManagedContext.defaultContext, true);
  }
  // ...
}
```
Our tests are contained within a `main()` top-level function as required by Dart in order to run our tests. The `setUp` function creates a list of `Book` types and using the `Query<T>` object we populate the database for each test. Calling the query object reopens the database during the test.

Let's create a test by replacing the `//...tests to go here` comment with the snippet below:

```dart
group("books controller", () {
  test("GET /books returns list of books", () async {
    // Arrange
    var request = app.client.request("/books");

    // Act
    var response = await request.get();

    // Assert
    expectResponse(response, 200,
        body: everyElement(partial(
            {
              "title": isString,
              "author": isString,
              "year": isInteger
            })));
    });
});
```
The `group` function is used for categorising related tests, similar to having the `describe` block if you've worked with the Jasmine BDD framework and `test` is similar to the `it` block.

Some further things to take note of:

1. Our first test creates a request from our `TestClient` object, performs the GET operation and runs our assertion on the response using the `expectResponse` _matcher method_. It accepts the response, status code and assertion under the `body` named parameter.
2. `everyElement` is another matcher method that allows us to run a check against each item in the response body, assuming it's a list.
3. `partial` makes an assertion against specific keys, provided the list item is a Map. We use this to save us checking every single key.
4. `isString` and `isInteger` are other inbuilt getters for ensuring the type is what we expect

Here's the next one for a POST request:

```dart
test("POST /books creates a new book", () async {
  var request = app.client.request("/books")
    ..json = {
      "title": "JavaScript: The Good Parts",
      "author": "Douglas Crockford",
      "year": 2008
    };
  expectResponse(await request.post(), 200,
      body: partial({
        "id": 4,
        "title": "JavaScript: The Good Parts",
    }));
});
```
The post request object accepts a body through the `json` property. This assumes the payload is a JSON string.

Below is our full automated test:

{% gist https://gist.github.com/graphicbeacon/d1371ee7d854df54a6dedfe5a2678010 %}

We can run our tests by doing:

```bash
dart test/books_controller_test.dart
```
And see the results below:
![All tests passing](https://thepracticaldev.s3.amazonaws.com/i/3ouofxj1aya7a1amp7zg.png)

## Conclusion

Our APIs are now covered by tests. I hope you learnt something useful and may find it worth considering Aqueduct for your next project.

Check out the reading materials below to understand Aqueduct testing in further detail. As always, **feedback is welcome**. Let me know what you liked, disliked and what you'd want to see next. I'd really be grateful for that.

And this concludes Part 4 of the series. The source code is [available on github](https://github.com/graphicbeacon/favereads). Stay tuned for bonus content.

## Further reading
1. [Writing tests in Aqueduct](https://aqueduct.io/docs/testing/tests/)
2. [Aqueduct test library documentation](https://www.dartdocs.org/documentation/aqueduct/latest/aqueduct.test/aqueduct.test-library.html)
3. [Dart "test" package](https://pub.dartlang.org/packages/test)

---

{% link https://dev.to/graphicbeacon/building-restful-web-apis-with-dart-aqueduct-and-postgresqlbonus-content-551g %}

---

_Originally posted on [Medium](https://itnext.io/building-restful-web-apis-with-dart-aqueduct-and-postgresql-part-4-testing-de3618515639)_