Search code examples
swiftpostgresqldockerunit-testingvapor

Why is my postgres table not existing when implementing Unit Tests?


I am starting to implement unit tests in a Vapor 4 project but the users postgres table is not being deleted/created after each tests. It works only for 1 test before it crashes them all.

Even though I am not implementing any tests, the compiler stop with this error:

caught error: "previousError(server: table "users" does not exist (DropErrorMsgNonExistent))"

My database is running with PostgreSQL in a Docker container. When I delete this container manually and run the tests again, there is no error. When I run it a second time, the error comes back.

Why is my table not being deleted after each tests?

This is my testing file, I have implemented a simple test to try out the plumbing:

import XCTVapor
@testable import App

class BaseXCTestCase: XCTestCase {

  var app: Application!

  override func setUpWithError() throws {
    try super.setUpWithError()
    app = Application(.testing)
    try configure(app).  // The error is caught here.
  }

  override func tearDownWithError() throws {
    app.shutdown()
    try super.tearDownWithError()
  }

  // This test crashes if everything goes well, but it
  // doesn't even run as the crashes appears in configure.swift.
  func test_zero() {
    XCTFail("Tests not yet implemented")
  }
}

This is my users table:

import Fluent

struct UserModelMigration_v1_0_0: Migration {

  func prepare(on database: Database) -> EventLoopFuture<Void> {
    
    database.schema("users")
      .id()
      .field("email", .string, .required)
      .field("password", .string, .required)
      .field("appleID", .string)
      .unique(on: "email")
      .create()
  }

  func revert(on database: Database) -> EventLoopFuture<Void> {
    database.schema("users").delete()
  }
}

This is a part of my configure.swift file where I setup the migration for the tests:

import Fluent
import FluentPostgresDriver
import Vapor

public func configure(_ app: Application) throws {
  // MARK: Database
  let databaseName: String
  let databasePort: Int

  if app.environment == .testing {
    databaseName = "db_testing"
    databasePort = 5434
  }
  else {
    databaseName = "db_development"
    databasePort = 5433
  }

  app.databases.use(.postgres(
    hostname: "localhost",
    port: databasePort,
    username: "username",
    password: "password",
    database: databaseName
  ), as: .psql)

  // MARK: Migrations
  app.migrations.add(UserModelMigration_v1_0_0())

  // MARK: Clear Database
  if app.environment == .testing {
    try app.autoRevert().wait()
  }

  if !app.environment.isRelease {
    try app.autoMigrate().wait()
  }
}

There are also two files where I added code to run the tests in the Docker container.

file: docker-compose-testing.yml

version: '3'
services:
  serverside-app:
    depends_on:
      - postgres
    build:
      context: .
      dockerfile: testing.Dockerfile
    environment:
      - DATABASE_HOST=postgres
      - DATABASE_PORT=5434
  postgres:
    image: "postgres"
    environment:
      - POSTGRES_DB=db_testing
      - POSTGRES_USER=username
      - POSTGRES_PASSWORD=password

file: testing.Dockerfile

FROM swift:5.3
WORKDIR /package
COPY . ./
CMD ["swift", "test", "--enable-test-discovery"]

Solution

  • Your revert will fail if the table doesn't exist. Changing it to try? app.autoRevert().wait() and your auto-migration to try? app.autoMigrate().wait() will ignore any errors.