Advanced iOS Summer Bundle

3 brand-new books on SwiftUI, Combine and Catalyst — $99.99 for a limited time!

Server-Side Swift: Testing on Linux

In this tutorial, you’ll test your server-side Swift apps on Linux, learning the differences between testing on macOS and Linux, and how to use Docker and Docker Compose.

Version

  • Swift 4.2, Ubuntu 18.04, Other

Testing is an important part of the software development process. Writing unit tests and automating them as much as possible allows you to develop and evolve your applications quickly.

In this tutorial, you’ll learn how to test your server-side Swift apps on Linux. You’ll see what the differences are between testing on macOS and Linux. Finally, you’ll learn how to use Docker and Docker Compose to easily test your apps on Linux locally.

Note: This tutorial requires Docker. Check out Docker on macOS: Getting Started to learn the basics of and how to install Docker.

Why Test on Linux?

Testing is a key part of any app development and deployment. You’ll find this helpful tutorial that covers how to test Vapor apps and how to write tests for server-side Swift projects.

Testing on Linux is important as well. You’d usually deploy your app to an operating system that is different from the one you used for development. It’s vital that you test your app on the same environment that you deploy on. Why is it important?

Foundation on Linux isn’t the same as Foundation on macOS. On macOS, it still uses the Objective-C framework, which has been thoroughly tested over the years. Linux uses the pure Swift Foundation framework, which isn’t as robust. The implementation status list shows that many features remain unimplemented on Linux. If you use these features, your app may crash. While the situation improves constantly, you must still ensure everything works as expected on Linux.

Declaring Tests on Linux

Like all server-side Swift apps, SPM (Swift Package Manager) plays an important part for testing your code. On iOS, Xcode links tests to a specific target. It configures a scheme to use that target and you run tests from within Xcode. The Objective-C runtime scans your project for XCTestCases and picks out the methods whose names begin with test.

On Linux, and with SPM, there’s no Objective-C runtime. There’s also no Xcode project to keep track of schemes or tests. You must declare them yourself.

Getting Started

To kick things off, start by downloading the materials for this tutorial using the Download Materials button at the top or bottom of this tutorial. The starter project contains a Vapor app with a number of tests. Open Package.swift. You’ll see a test target defined in the targets array:

.testTarget(name: "AppTests", dependencies: ["App"])

This defines a testTarget type with a dependency on App. Tests must live in the Tests/ directory. In this case, that’s Tests/AppTests.

Checking the Tests

The tests connect to a PostgreSQL database, with the configuration defined in configure.swift in Sources/App. The tests are expecting a database on port 5432 called vapor-test, which accepts a username of vapor and a password of password. This helps avoid conflicting Docker containers when running the app locally. In Terminal, enter the following:

docker run --name postgres-test -e POSTGRES_DB=vapor-test \
  -e POSTGRES_USER=vapor -e POSTGRES_PASSWORD=password \
  -p 5433:5432 -d postgres

Here’s what this does:

  • Run a new container named postgres-test.
  • Specify the database name, username and password through environment variables.
  • Allow apps to connect to the Postgres server by mapping the external port 5433 to the container’s port 5432.
  • Run the server in the background as a daemon.
  • Use the Docker image named postgres for this container. If the image isn’t present on your machine, Docker automatically downloads it.

Then run the tests. In Terminal make sure you’re in the starter directory, then enter the following:

swift test

This pulls down the required dependencies, compiles the project and runs the tests. You’ll see all the tests complete successfully:

swift test running successfully

To clean up, in Terminal, run the following commands:

# 1
docker stop postgres-test
# 2
docker rm postgres-test

Here’s what the commands do:

  1. Stop the postgres-test container that you ran your tests in.
  2. Delete the postgres-test container now that it’s no longer running.

LinuxMain.swift

On macOS, swift test still uses Objective-C to find the tests to run. On Linux, there’s no Objective-C runtime to discover your XCTestCases. You must point Swift in the right direction.

LinuxMain.swift is the file that tells where the test cases are on Linux. Swift executes that file when you call swift test on Linux. An example for this project is the following:

import XCTest
// 1
@testable import AppTests

// 2
XCTMain([
  testCase(AcronymTests.allTests),
  testCase(CategoryTests.allTests),
  testCase(UserTests.allTests)
])

This file is the test equivalent of main.swift. Here’s what it does:

  1. Import the AppTests module, which contains your tests.
  2. Provide an array of tests for each XCTestCase to XCTMain(_:). These are the tests executed when testing your app on Linux.

You must provide an array for each XCTestCase. By convention, you call this array __allTests. It contains a list of tuples consisting of the name of the test and the test itself. For UserTests this looks like the following:

static let __allTests = [
  ("testUsersCanBeRetrievedFromAPI", testUsersCanBeRetrievedFromAPI),
  ("testUserCanBeSavedWithAPI", testUserCanBeSavedWithAPI),
  ("testGettingASingleUserFromTheAPI", testGettingASingleUserFromTheAPI),
  ("testGettingAUsersAcronymsFromTheAPI", testGettingAUsersAcronymsFromTheAPI)
]

When you call swift test on Linux, the test executable uses this array to determine which tests to run.

Generating a Test Manifest

Currently, you must create and maintain a LinuxMain.swift and the test arrays manually. This leaves a lot of room for error and it’s time consuming. Swift 4.1 introduced a new tool generate-linuxmain to help. In Terminal, from the root folder of the starter project, type the following:

swift test --generate-linuxmain

This generates LinuxMain.swift in the Tests directory for you. The command also generates XCTestManifests.swift in Tests/AppTests. This file contains extensions for all test cases in your module, defining each test. This saves time and avoids mistakes when declaring your tests for Linux.

Note: This command runs on both Linux and macOS. However, because SPM relies on the Objective-C runtime to discover the tests, the command only works on macOS. On Linux, the command executes successfully, but does not generate any manifests or files for you. This makes it difficult to integrate into continuous integration for Linux. It also means you must still run the command manually on macOS.

Running Tests on Linux

With the test manifest now generated, you can run your tests on Linux. Early feedback is always valuable in software development and running tests on Linux is no exception. Using a Continuous Integration system to automatically test on Linux is vital, but what happens if you want to test on Linux on your Mac?

Generating a Dockerfile

Well, you’ve already used Linux for the PostgreSQL test database using Docker! So you can also use Docker to run your tests in a Linux environment. In the top level project starter directory, create a new file called Dockerfile (with no extension). Open the file in a text editor and add the following:

# 1
FROM swift:4.2

# 2
WORKDIR /package
# 3
COPY . ./
# 4
RUN swift package resolve
RUN swift package clean
# 5
CMD ["swift", "test"]

Here’s what the Dockerfile does:

  1. Use the official Swift 4.2 image.
  2. Set the working directory to /package.
  3. Copy the contents of the current directory into /package in the container.
  4. Fetch the dependencies and clean up the project’s build artifacts.
  5. Set the default command to swift test. This is the command Docker executes when you run the Dockerfile.

Using Docker Compose

As described earlier, the tests need a PostgreSQL database in order to run. By default, Docker containers can’t see each other. However, Docker has a tool, Docker Compose, designed to link together different containers for testing and running apps. Create a new file called docker-compose.yml in the top level starter project directory. Open the file in an editor and add the following:

# 1
version: '3'
# 2
services:
  # 3
  til-app:
    # 4
    depends_on:
      - postgres
    # 5
    build: .
    # 6
    environment:
      - DATABASE_HOSTNAME=postgres
      - DATABASE_PORT=5432
  # 7
  postgres:
    # 8
    image: "postgres"
    # 9
    environment:
      - POSTGRES_DB=vapor-test
      - POSTGRES_USER=vapor
      - POSTGRES_PASSWORD=password

Here’s what this does:

  1. Specify the Docker Compose version.
  2. Define the services for this app.
  3. Define a service for the TIL app.
  4. Set a dependency on the Postgres container, so Docker Compose starts the Postgres container first.
  5. Build the Dockerfile in the current directory — the Dockerfile you just created.
  6. Inject the DATABASE_HOSTNAME environment variable. Docker Compose has an internal DNS resolver. This
    allows the til-app container to connect to the postgres container with the hostname postgres. Also set the port for the database to use in tests.
  7. Define a service for the Postgres container.
  8. Use the standard Postgres image.
  9. Set the same environment variables as used at the start of the tutorial for the test database.

To test your app in Linux, in Terminal, type the following:

# 1
docker-compose build
# 2
docker-compose up --abort-on-container-exit

Here’s what this does:

  1. Build the different Docker containers.
  2. Spin up the different containers and run the tests. --abort-on-container-exit tells Docker Compose to stop the postgres container when the til-app container stops. The postgres container used for this test is different from, and doesn’t conflict with, the one you ran earlier for testing on macOS.

When the tests finish running, you’ll see the output in Terminal with all tests passing:

Running tests with Docker Compose

Note: You could also run the tests on a real Linux machine. After generating the LinuxMain.swift file, you can move the whole starter folder to the Linux machine. Remember to remove the .build folder, so that dependencies are recompiled correctly when you run swift test. Your Linux machine will need PostgreSQL (as configured in the Docker example above) and the following packages installed: libssl-dev, libz-dev and pkg-config, which you can install via sudo apt install libssl-dev libz-dev pkg-config.

Where to Go From Here?

Writing tests and running them on the platforms you’ll use in production is important. It gives you confidence your code will work when you deploy your application. Having a good test suite allows you to evolve and adapt your apps quickly.

In this tutorial, you’ve learned how you to test your server-side Swift apps on Linux to ensure that they work correctly. Making use of Docker and Docker Compose makes testing on Linux simple. You can add different dependencies and services as needed, such as Redis or other databases.

Feel free to take a look at the final project using the Download Materials button at the top or bottom of this tutorial. If you have any questions, join us on the forums below!

Add a rating for this content

Contributors

Comments