【问题标题】:Why is my database not being cleared after each unit test?为什么每次单元测试后我的数据库都没有被清除?
【发布时间】:2021-12-28 19:59:59
【问题描述】:

我正在尝试在没有创建用户并且可以第一次注册时运行测试。测试第一次完美运行并通过。问题是,如果我第二次运行它,我的容器不会被正确删除,我们最终会得到 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())
}

这就是我恢复所有模型的方式。 AddressModelCompanyModel@OptionalChildUserModel。看来问题是从这里来的,但我不能指出来。

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"]

【问题讨论】:

  • 这是一个码头工人还是一个快速的问题?另外,您似乎还问了多个问题。
  • 关于没有被删除的容器,执行后可以随时清理容器-serverfault.com/questions/750175/…

标签: swift unit-testing vapor


【解决方案1】:

对我来说突出的问题是您的还原方法似乎实际上并未删除数据库中的数据或删除表。

我有类似的测试使用 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()
  }
}

【讨论】:

    【解决方案2】:

    您为什么不将您的 App 类放在代表 sut(被测系统)的测试 var 中并覆盖您的测试的 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)
        }
    
    }
    

    无论如何,我也对您的测试增加了期望,因为它似乎正在以旧方式完成一些异步工作。

    【讨论】:

    • 应用程序已经在setUp和tearDown中。我将它们放在测试方法中以简化 StackOverflow 中的代码,但它的工作原理相同。您提供的代码没有在您这边进行测试,否则,您会注意到它由于多种原因无法正常工作。请确保在发布您的答案之前先通过测试。
    • 所以说清楚,你有什么要求?我们必须为您提供一个在不知道它测试什么的情况下通过的测试,或者您的问题是关于“我如何编写一个在完成后重置 SUT 状态的测试”?您提供的代码似乎有一个超出测试范围的断言,因为这样的断言被放入一个您可能没有等待的闭包中。
    • 如前所述,我想在每次单元测试后清除 Docker 容器中的所有数据。
    • 然后你想运行代码来清除tearDown()中的Docker容器中的数据库条目。或者您可以模拟此类服务,使其使用内存存储而不是持久存储,并仅在您的测试中使用该模拟。例如要测试CoreData,一种常见的方法是在测试中创建它的堆栈作为内存。无论如何,如果没有您的Application 类的更多详细信息,基本上不可能理解哪种方法是可行的,以及如何最终将数据库恢复为空状态。
    猜你喜欢
    • 1970-01-01
    • 2023-01-11
    • 2011-11-04
    • 1970-01-01
    • 1970-01-01
    • 2019-11-26
    • 1970-01-01
    • 1970-01-01
    • 2021-09-22
    相关资源
    最近更新 更多