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

Time:11-21

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()
  }
}
  • Related