为什么我的数据库在每次单元测试后都没有被清除?

Why is my database not being cleared after each unit test?

我正在尝试 运行 在没有创建用户并且可以首次注册的情况下进行测试。测试 运行 第一次完美通过。问题是,如果我第二次 运行 它,我的容器没有被正确删除,我们最终得到 HTTPStatus.conflict 而不是 .ok,所以用户确实已经存在。

我正在 运行在我的 MacBook 上安装一个 Docker 容器,其中设置了 docker-compose-testingdocker-composetesting.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())
}

这就是我恢复所有模型的方式。 AddressModelCompanyModelUserModel@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()
  }
}