I am trying to run a tests when no user is created and one can sign up for the first time. The test runs perfectly the first time and passes. The thing is that if I run it a second time, my container is not deleted properly and we end up with a HTTPStatus.conflict
instead of .ok
, so the user does already exist.
I am running a Docker container on my MacBook with docker-compose-testing
, docker-compose
and testing.Dockerfile
set.
This error is also triggered when running the test:
caught error: "server: syntax error at end of input (scanner_yyerror)"
What is it missing here? Wouldn't autoRevert()
and autoMigrate()
clear my database?
func testSignUpRoute_userDoesNotExistInDatabase_userSignUpAndHTTPStatusIsOk() throws {
// Configuration in setUp and tearDown.
var app = Application(.testing)
defer { app.shutdown() }
try configure(app)
try app.autoRevert().wait()
try app.autoMigrate().wait()
// Given
let expected: HTTPStatus = .ok
let headers = [
"email": "[email protected]",
"password": "fooEncrypted"
]
// When
try app.test(.POST, "users/signup") { request in
try request.content.encode(headers)
} afterResponse: { response in
let result = response.status
// Then
XCTAssertEqual(result, expected, "Response status must be \(expected)")
}
}
This is the order in which my migration happens in the configure.swift
file:
public func configure(_ app: Application) throws {
app.migrations.add(UserModelMigration_v1_0_0())
app.migrations.add(AddressModelMigration_v1_0_0())
}
And the this is how I revert all my models. AddressModel
and CompanyModel
are @OptionalChild
of the UserModel
. It seems like the issue comes from here, but I can not point it out.
struct UserModelMigration_v1_0_0: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema(UserModel.schema)
.id()
.field(UserModel.Key.email, .string, .required)
.field(UserModel.Key.password, .string, .required)
.unique(on: UserModel.Key.email)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database
.schema(UserModel.schema)
.update()
}
}
struct AddressModelMigration_v1_0_0: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema(AddressModel.schema)
.id()
.field(AddressModel.Key.streetLineOne, .string, .required)
.field(AddressModel.Key.city, .string, .required)
.field(AddressModel.Key.userID, .uuid, .required,
.references(UserModel.schema, .id,
onDelete: .cascade,
onUpdate: .cascade))
.unique(on: AddressModel.Key.userID)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database
.schema(AddressModel.schema)
.update()
}
}
This is my docker-compose-testing.yml
file
version: '3'
services:
testingonlinux:
depends_on:
- postgres
build:
context: .
dockerfile: testing.Dockerfile
environment:
- DATABASE_HOST=postgres
- DATABASE_PORT=5434
postgres:
image: "postgres"
environment:
- POSTGRES_DB=foo_db_testing
- POSTGRES_USER=foo_user
- POSTGRES_PASSWORD=foo_password
This is my docker-compose.yml
file
version: '3.8'
volumes:
db_data:
x-shared_environment: &shared_environment
LOG_LEVEL: ${LOG_LEVEL:-debug}
DATABASE_HOST: db
DATABASE_NAME: vapor_database
DATABASE_USERNAME: vapor_username
DATABASE_PASSWORD: vapor_password
services:
app:
image: FooServerSide:latest
build:
context: .
environment:
<<: *shared_environment
depends_on:
- db
ports:
- '8080:8080'
# user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user.
command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
migrate:
image: FooServerSide:latest
build:
context: .
environment:
<<: *shared_environment
depends_on:
- db
command: ["migrate", "--yes"]
deploy:
replicas: 0
revert:
image: FooServerSide:latest
build:
context: .
environment:
<<: *shared_environment
depends_on:
- db
command: ["migrate", "--revert", "--yes"]
deploy:
replicas: 0
db:
image: postgres:12-alpine
volumes:
- db_data:/var/lib/postgresql/data/pgdata
environment:
PGDATA: /var/lib/postgresql/data/pgdata
POSTGRES_USER: vapor_username
POSTGRES_PASSWORD: vapor_password
POSTGRES_DB: vapor_database
ports:
- '5432:5432'
And my testing.Dockerfile
FROM swift:5.5
WORKDIR /package
COPY . ./
CMD ["swift", "test", "--enable-test-discovery"]
CodePudding user response:
Why don't you just put your App class in a var of your tests representing the sut (system under test) and override your test's setup()
and tearDown()
methods?
As in:
final class AppTests: XCTestCase {
var sut: Application!
override setup() {
super.setup()
sut = Application(.testing)
}
override tearDown() {
sut.shutdown()
sut = nil
super.tearDown()
}
// MARK: - Given
// MARK: - When
func whenMigrationsAreSet() async throws {
sut.migrations.add(UserModelMigration_v1_0_0())
sut.migrations.add(AddressModelMigration_v1_0_0())
sut.migrations.add(CompanyModelMigration_v1_0_0())
try await app.autoRevert()
try await app.autoMigrate()
}
// MARK: - Tests
func testSignUpRoute_whenUserDoesNotExist_thenUserSignUpAndHTTPStatusIsOk() async throws {
try await whenMigrationsAreSet()
let headers = [
"email": "[email protected]",
"password": "fooEncrypted",
]
let requestExpectation = expectation("request completed")
let responseExpectation = expectation("got response")
var result: HTTPStatus?
try sut.test(.POST, "users/signup") { request in
try request.content.encode(headers)
requestExpectation.fullfill()
} afterResponse { response in
result = response.status
responseExpectation.fullfill()
}
wait(for: [requestExpectation, responseExpectation], timeout: 0.5, enforceOrder: true)
XCTAssertEqual(result, .ok)
}
}
Anyway I've also added expectations to your test since it seems it was doing some async work with completions in the old fashion.
CodePudding user response:
The issue that stands out to me is that your revert methods don't seem to actually be deleting the data in the database or removing the table.
I have similar tests that use the revert functionality within Vapor and the migrations look like the following:
public struct ListV1: Migration {
public func prepare(on database: Database) -> EventLoopFuture<Void> {
return database.schema("Lists")
.id()
.field("listId", .int, .required, .custom("UNIQUE"))
.field("listString", .string, .required)
.field("createdAt", .datetime)
.field("updatedAt", .datetime)
.create()
}
public func revert(on database: Database) -> EventLoopFuture<Void> {
return database.schema("Lists").delete()
}
}
Changing your revert function to use .delete()
(shown below) may resolve your issue:
struct AddressModelMigration_v1_0_0: Migration {
...
func revert(on database: Database) -> EventLoopFuture<Void> {
database
.schema(AddressModel.schema)
.delete()
}
}