为什么我的数据库在每次单元测试后都没有被清除?
Why is my database not being cleared after each unit test?
我正在尝试 运行 在没有创建用户并且可以首次注册的情况下进行测试。测试 运行 第一次完美通过。问题是,如果我第二次 运行 它,我的容器没有被正确删除,我们最终得到 HTTPStatus.conflict
而不是 .ok
,所以用户确实已经存在。
我正在 运行在我的 MacBook 上安装一个 Docker 容器,其中设置了 docker-compose-testing
、docker-compose
和 testing.Dockerfile
。
运行测试时也会触发此错误:
caught error: "server: syntax error at end of input (scanner_yyerror)"
这里缺少什么? autoRevert()
和 autoMigrate()
不会清除我的数据库吗?
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": "foo@email.com",
"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)")
}
}
这是我的迁移在 configure.swift
文件中发生的顺序:
public func configure(_ app: Application) throws {
app.migrations.add(UserModelMigration_v1_0_0())
app.migrations.add(AddressModelMigration_v1_0_0())
}
这就是我恢复所有模型的方式。 AddressModel
和 CompanyModel
是 UserModel
的 @OptionalChild
。好像问题出在这里,但我不能指出。
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()
}
}
这是我的 docker-compose-testing.yml
文件
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
这是我的 docker-compose.yml
文件
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'
还有我的testing.Dockerfile
FROM swift:5.5
WORKDIR /package
COPY . ./
CMD ["swift", "test", "--enable-test-discovery"]
你为什么不把你的应用 class 放在代表 sut(被测系统)的测试变量中并覆盖你的测试 setup()
和 tearDown()
方法?
如:
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": "foo@email.com",
"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)
}
}
无论如何,我也对您的测试增加了期望,因为它似乎正在以旧方式完成一些异步工作。
对我来说突出的问题是你的恢复方法似乎并没有真正删除数据库中的数据或删除 table。
我有类似的测试,使用 Vapor 中的还原功能,迁移如下所示:
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()
}
}
将还原函数更改为使用 .delete()
(如下所示)可能会解决您的问题:
struct AddressModelMigration_v1_0_0: Migration {
...
func revert(on database: Database) -> EventLoopFuture<Void> {
database
.schema(AddressModel.schema)
.delete()
}
}
我正在尝试 运行 在没有创建用户并且可以首次注册的情况下进行测试。测试 运行 第一次完美通过。问题是,如果我第二次 运行 它,我的容器没有被正确删除,我们最终得到 HTTPStatus.conflict
而不是 .ok
,所以用户确实已经存在。
我正在 运行在我的 MacBook 上安装一个 Docker 容器,其中设置了 docker-compose-testing
、docker-compose
和 testing.Dockerfile
。
运行测试时也会触发此错误:
caught error: "server: syntax error at end of input (scanner_yyerror)"
这里缺少什么? autoRevert()
和 autoMigrate()
不会清除我的数据库吗?
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": "foo@email.com",
"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)")
}
}
这是我的迁移在 configure.swift
文件中发生的顺序:
public func configure(_ app: Application) throws {
app.migrations.add(UserModelMigration_v1_0_0())
app.migrations.add(AddressModelMigration_v1_0_0())
}
这就是我恢复所有模型的方式。 AddressModel
和 CompanyModel
是 UserModel
的 @OptionalChild
。好像问题出在这里,但我不能指出。
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()
}
}
这是我的 docker-compose-testing.yml
文件
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
这是我的 docker-compose.yml
文件
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'
还有我的testing.Dockerfile
FROM swift:5.5
WORKDIR /package
COPY . ./
CMD ["swift", "test", "--enable-test-discovery"]
你为什么不把你的应用 class 放在代表 sut(被测系统)的测试变量中并覆盖你的测试 setup()
和 tearDown()
方法?
如:
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": "foo@email.com",
"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)
}
}
无论如何,我也对您的测试增加了期望,因为它似乎正在以旧方式完成一些异步工作。
对我来说突出的问题是你的恢复方法似乎并没有真正删除数据库中的数据或删除 table。
我有类似的测试,使用 Vapor 中的还原功能,迁移如下所示:
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()
}
}
将还原函数更改为使用 .delete()
(如下所示)可能会解决您的问题:
struct AddressModelMigration_v1_0_0: Migration {
...
func revert(on database: Database) -> EventLoopFuture<Void> {
database
.schema(AddressModel.schema)
.delete()
}
}