Introduction

Vapor makes it easy to write unit tests for server-side applications. I particularly find it fantastic that tests can run in memory, not even opening a port. Also, the database can be changed quite easily to use in-memory SQLite:

app.databases.use(.sqlite(.memory), as: .sqlite)

Doing that would allow a unit test to be quite lightweight. However, I had some issues with that approach. This post is about explaining what that issue was and how I ended up changing my unit tests to use PostgreSQL directly.

Note that I am not an expert in databases, so if you notice my understanding was wrong, please let me know.

Why use Postgres in Unit Tests

If you have an application that uses PostgreSQL, it might be a good idea to use it also in unit tests. I realized that when I had two migrations. The first created a schema “blog_posts” with some fields and the other created a new field on the schema. The second migration was quite simple:

try await db.schema("blog_posts")
  .field("image_key", .string)
  .update()

However, I also had the idea of making the new field unique:

try await db.schema("blog_posts")
    .unique(on: "image_key")
    .update()

When running the app, that worked fine… with Postgres. SQLite has some limitations on altering a table which caused issues. I looked up the docs for ALTER TABLE, and I found out that adding a column had a few restrictions, among which:

The column may not have a PRIMARY KEY or UNIQUE constraint.

This was quite a let-down and I thought that even if I changed things to have it working somehow with SQLite for this case, it would be quite likely that I would have other issues in the future as migrations are created.

At some point, I even had an issue in which the app worked with SQLite, but failed with PostgreSQL due to a type error. It was a bug in one of my types that SQLite accepted, as it is more permissive by default, but PostgreSQL does not.

Those issues reminded me of when I had written some unit tests in .NET Core. At the time, I noticed the tests created temporary databases, which usually I never saw except for some cases in which a crash in the test would stop its clean-up procedure from running. I thought of doing the same for Vapor, and this post will show you how.

How to do it?

Setting up PostgreSQL with Vapor is easy enough, but when we are doing unit tests we should care about them not interfering with each other. They should all be at the same starting point regarding database data.

My approach was creating a class that inherits from XCTestCase, which I called AppTestCase. It defines a variable var app: Application! which will be available for unit tests that inherit from AppTestCase which will already be connected to our new database. In AppTestCase, we also override setUpWithError and tearDownWithError. The former creates a temporary database, while the latter destroys it.

The temporary database uses a random string, which uses code I found on StackOverflow. One issue I had was that by default PostgreSQL is case-insensitive, and the random string had some upper-case characters. This caused some issues like not being able to clean up the database. I think it couldn’t connect either. This was fixed by lower-casing database name.

Once you have that, you just need to make sure to have a PostgreSQL instance running and have it connected. The docs explain that pretty well. For testing, you can just inherit from AppTestCase and use the database that was created.

Code

You can find an example of the approach mentioned in this post in the PetClicker repository tests. That’s just a personal project I am working on which should eventually track how many clicks pet images had.

The main code for AppTestCase is available below as well:

import FluentPostgresDriver
import XCTVapor

@testable import App

open class AppTestCase: XCTestCase {
  var databaseName: String!
  var databaseId: DatabaseID { DatabaseID(string: databaseName) }
  var app: Application!

  open override func setUpWithError() throws {
    app = Application(.testing)
    try configure(app)
    databaseName = "pet_clicker_test_\(String.random(length: 10))".lowercased()

    let postgres = app.db(.psql) as! PostgresDatabase

    _ = try postgres.simpleQuery("CREATE DATABASE \(databaseName!)").wait()

    app.databases.use(
      .postgres(
        hostname: Environment.get("DATABASE_HOST") ?? "localhost",
        port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:))
          ?? PostgresConfiguration.ianaPortNumber,
        username: Environment.get("DATABASE_USERNAME") ?? "vapor_username",
        password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password",
        database: databaseName
      ),
      as: databaseId
    )
    app.databases.default(to: databaseId)
    try app.autoMigrate().wait()

    try super.setUpWithError()
  }

  open override func tearDownWithError() throws {
    app.shutdown()

    let clearDatabaseApp = Application(.testing)
    try configure(clearDatabaseApp)
    let postgres = clearDatabaseApp.db(.psql) as! PostgresDatabase
    _ = try postgres.simpleQuery("DROP DATABASE \(databaseName!)").wait()
    clearDatabaseApp.shutdown()

    try super.tearDownWithError()
  }
}
extension String {
  /// Generates a random string with given length
  ///
  /// - Source: [StackOverflow](https://stackoverflow.com/a/26845710)
  /// - License: [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
  static func random(length: Int) -> String {
    let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    return String((0..<length).map { _ in letters.randomElement()! })
  }
}

Conclusion

We saw that even though writing unit tests with SQLite is interesting due to its lightweight nature, it has limitations when it comes to migrations and even some type errors may go unnoticed. Using PostgreSQL is an alternative for that and it is relatively simple to set up.

For more code examples, you can see the PetClicker repo. It uses this approach for unit tests and also runs them on GitLab’s CI.