I want to write some integration tests for Vapor 3 server and I need to have clean Postgre database each time I run my tests. How can I achieve this? It seems migrations isn't the right way to go as they've been running once if database doesn't exist yet.
-
Have you tried using raw SQL queries ? Also, `drop table` is part of the SQL package – nathan Nov 20 '18 at 14:33
-
@nathan and what is the proper place to run this query in vapor3 project? – m8labs Nov 20 '18 at 16:42
-
1Using XCTestCase ? https://stackoverflow.com/questions/29822457/how-to-run-one-time-setup-code-before-executing-any-xctest – nathan Nov 20 '18 at 16:50
5 Answers
Have a look at https://github.com/raywenderlich/vapor-til/tree/master/Tests
This requires a DB to be running before you run the tests, but it reverts all migrations at the start of each test run, which gives you a clean DB each time. (Specifically here)
There's also a docker-compose.yml
in the root directory for spinning up a completely isolated test environment on Linux

- 5,146
- 11
- 23
-
I've inspected sources and have some concerns - isn't it extremely wasteful to run different instance of the Application (2 times in a row) before _each_ test method? Why don't they use "override class func setUp()" instead?... – m8labs Nov 22 '18 at 15:11
-
Running the commands (revert and prepare) require an application to boot up. When you provide the application a command it will exit upon completion, so if you do it the way above you don't have much choice for integration tests – 0xTim Nov 23 '18 at 13:01
-
I am using the method from the RW repo as well, and find that tests are very slow due to all the reverting re-migrating going on. Have 8 tests and it takes 8 seconds to run. An identical backend I wrote in Python/Django with identical tests takes only 0.3 seconds.. – Kevin Renskers Mar 30 '19 at 23:08
I've found a solution that is less resource-intensive, then reverting all migrations every time.
RSpec has a configuration (use_transactional_fixtures
) that allows wrapping every test in an SQL transaction. When testing is over it will rollback the transaction and in consequence revert all the changes that happened during testing. Relevant documentation is here.
We can implement a similar solution in Vapor. My example test looks like this.
final class VaporTests: XCTestCase {
var app: Application!
override func setUp() {
super.setUp()
app = try! Application.buildForTesting()
let conn = try! app.requestPooledConnection(to: .psql).wait()
try! conn.simpleQuery("BEGIN TRANSACTION").wait()
try! app.releasePooledConnection(conn, to: .psql)
}
override func tearDown() {
let conn = try! app.requestPooledConnection(to: .psql).wait()
try! conn.simpleQuery("ROLLBACK").wait()
try! app.releasePooledConnection(conn, to: .psql)
super.tearDown()
}
func testExample() throws {
let request = HTTPRequest(method: .GET, url: "my/endpoint/example")
let wrapper = Request(http: request, using: app)
let response = try ExampleController().example(wrapper).wait()
XCTAssertEqual(response, .ok)
}
}
To make sure that I don't encounter issues with concurrency I'm limiting database pool to 1 connection in the test application.
func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
// ... other configurations
let poolConfig = DatabaseConnectionPoolConfig(maxConnections: 1)
services.register(poolConfig)
}
Many thanks to Jakub Jatczak for helping me to find out how this happens in Rails.

- 6,124
- 3
- 34
- 48
-
Strange, when I try this, the tests never run, they simply hang forever on migrating the database. – Kevin Renskers Mar 30 '19 at 23:12
-
When I saw tests hanging, that usually meant, that I blocked the only accessible connection without releasing it. Make sure that your code around tests doesn't cause deadlock. – Tomasz Bąk Apr 01 '19 at 09:12
Quite late to the party but following way also does the revert
and migrate
command work. This code does the similar commands as answer given by @0xTim. But I have made use of Console.framework
:
Mostly we use a configure.swift
file like below:
import FluentPostgreSQL
import Vapor
public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
// Register providers first
try services.register(FluentPostgreSQLProvider())
...
/// Configure commands
var commandConfig = CommandConfig.default()
commandConfig.useFluentCommands()
services.register(commandConfig)
...
/// Configure migrations
services.register { container -> MigrationConfig in
var migrationConfig = MigrationConfig()
try migrate(migrations: &migrationConfig)
return migrationConfig
}
}
Quite late to the party but following code does execute revert and migrate
commands: (I am using Quick
and Nimble
so beforeSuite
. Commented code is there because unless you use above configure.swift
you can just uncomment the code and make use of CommandConfig
directly.)
import Quick
import Vapor
import Console
import FluentPostgreSQL
...
configuration.beforeSuite {
let console: Console = Terminal()
// var commandConfig = CommandConfig()
// commandConfig.use(RevertCommand(), as: "revert")
// commandConfig.use(MigrateCommand(), as: "migrate")
var config = Config.default()
var env = Environment.testing
var services = Services.default()
do {
// try App.configure(&config, &env, &services)
let container = try Application(config: config, environment: env, services: services)
let commandConfig = try container.make(CommandConfig.self)
let commands = try commandConfig.resolve(for: container).group()
var input = CommandInput(arguments: ["vapor","revert","--all", "-y"])
try console.run(commands, input: &input, on: container).wait()
input = CommandInput(arguments: ["vapor","migrate","-y"])
try console.run(commands, input: &input, on: container).wait()
} catch let error {
console.error(error.localizedDescription)
exit(1)
}
}

- 87
- 10
For thoses who are seeking another approach that doesnt involve registering new migrations ( and, to me, adding more code complexity ) you can use a Pre-Action script for tests target ( ⌘ + < )
By using a bash script you can create a brand new postgresql database that will be used to build the project for tests only :
# Variables
export IS_TEST=true
export DB_USERNAME="`whoami`"
export DB_DBNAME="BARTENDER_TEST_DB"
#Creating dedicated Postgres DB
echo "deleting & recreating $DB_DBNAME for user $DB_USERNAME"
psql postgres<< EOF
DROP DATABASE "$DB_DBNAME";
CREATE DATABASE "$DB_DBNAME";
\list
EOF
Then in configure.swift
file you create a PostgreSQLDatabaseConfig
that matches the newly created database
if let _ = Environment.get("IS_TEST") { // IS_TEST is defined only in Pre-Action script
guard let username = Environment.get("DB_USERNAME") else {
fatalError("Failed to create PostgresConfig - DB_USERNAME in Environment variables")
}
guard let databasename = Environment.get("DB_DBNAME") else {
fatalError("Failed to create PostgresConfig - DB_DBNAME in Environment variables")
}
postgresqlConfig = PostgreSQLDatabaseConfig(
hostname: "127.0.0.1",
port: 5432,
username: username,
database: databasename,
password: nil
)
}
else { /* your other config here */ }
let database = PostgreSQLDatabase(config: postgresqlConfig)
...
The big advantage I found in this it that I can even trigger a vapor build
and vapor run
from another project that will create a brand new Webservice environment for my continuous integration testings, just by inserting the correct environment variables

- 2,268
- 3
- 28
- 38
Simplest way I found was to add PostgresKit to the test target and use it to setup a connection to call my "clean-up" queries.
@testable import App
import Vapor
import XCTest
import PostgresKit
final class UserTests: XCTestCase {
var pools: EventLoopGroupConnectionPool<PostgresConnectionSource>!
var postgresDb: PostgresDatabase!
var eventLoopGroup: EventLoopGroup!
override func setUp() {
let configuration = PostgresConfiguration(
hostname: "localhost",
username: "postgres",
password: "password",
database: "db_name"
)
eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
pools = EventLoopGroupConnectionPool(
source: PostgresConnectionSource(configuration: configuration),
on: eventLoopGroup
)
postgresDb = pools.database(logger: Logger.init(label: "TestLogger"))
}
override func tearDown() {
let _ = try! postgresDb.query("DELETE FROM \(User.schema)").wait()
try! pools.syncShutdownGracefully()
try! eventLoopGroup.syncShutdownGracefully()
}
func testUploadUser() throws {
let app = Application(.testing)
defer { app.shutdown() }
try configure(app)
try app.testable(method: .running).test(.POST, "api/users", beforeRequest: { req in
try req.content.encode(["firstName" : "Dwide", "lastName" : "Shrewd"])
}, afterResponse: { res in
XCTAssertEqual(res.status, .ok)
let user = try res.content.decode(User.self)
XCTAssertEqual(user, User(id: user.id, firstName: "Dwide", lastName: "Shrewd"))
})
}
}
And this is my Package.swift
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "MyVaporProject",
platforms: [
.macOS(.v10_15)
],
dependencies: [
// A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"),
.package(url: "https://github.com/vapor/postgres-kit.git", from: "2.0.0")
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
.product(name: "Vapor", package: "vapor")
],
swiftSettings: [
// Enable better optimizations when building in Release configuration. Despite the use of
// the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release
// builds. See <https://github.com/swift-server/guides#building-for-production> for details.
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]
),
.target(name: "Run", dependencies: [.target(name: "App")]),
.testTarget(
name: "AppTests",
dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
.product(name: "PostgresKit", package: "postgres-kit")
]
)
]
)
As in previous answers, this requires a stood-up Postgres database, already migrated, ready to take connections before the tests start.

- 1,153
- 1
- 16
- 23