Search code examples
code-signingxctestxcode10macos-mojavewiremock

How can I launch WireMock for XCTest UI tests under Xcode 10 and macOS 10.14?


We are using a standalone wiremock instance as a mock server for our Xcode UI tests. We have an test observer class which is responsible for spinning up this instance (if required) and tearing it down upon completion of the test run. The code for the observer is as follows:

import AppKit
import XCTest
import WiremockClient

class SSUITestObserver: NSObject, XCTestObservation {

    enum TestObserverError : Error {
        case MockServerStartupError(String)
    }

    lazy var testBundleURL: URL = Bundle(for: SSUITestCase.self).bundleURL
    lazy var testBundleBinURL: URL = self.testBundleURL.appendingPathComponent("..", isDirectory: true)
    lazy var mockServerHomeURL: URL = self.testBundleURL.appendingPathComponent("Contents/Resources/", isDirectory: true)
    lazy var mockServerJarURL: URL = self.mockServerHomeURL.appendingPathComponent("wiremock-standalone-2.18.0.jar", isDirectory: false)

    override init() {
        super.init()

        NSLog("UI Test Observer Initialized")
        XCTestObservationCenter.shared.addTestObserver(self)
    }

    func testBundleWillStart(_ testBundle: Bundle) {
        NSLog("***Test Bundle starting")
        do {
            // Start the Wiremock server
            try ensureMockServerIsRunning()
        } catch {
            fatalError("\n Failed during test bundle setup: \(error)\n")
        }


    }

    public func testBundleDidFinish(_ testBundle: Bundle) {
        NSLog("***Test Bundle completed")
        stopMockServer()
    }

    func ensureMockServerIsRunning() throws {
        WiremockClient.baseURL = SSUIIntegrationTestCase.mockServerAddress

        guard !WiremockClient.isServerRunning() else { return }

        let args = ["-jar",
                    self.mockServerJarURL.path,
                    "--port", "3000",
                    "--root-dir", self.mockServerHomeURL.path]

        _ = Process.launchedProcess(launchPath: "/usr/bin/java", arguments: args)

        for _ in 1...9 {
            if WiremockClient.isServerRunning() { return }
            sleep(1)
        }

        throw TestObserverError.MockServerStartupError("Error staring up the mock server instance!")
    }

    func stopMockServer() {
        WiremockClient.shutdownServer()
    }

    func resetMockServerStubs() {
        WiremockClient.reset()
    }
}

All was well until I moved to macOS 10.14. Formerly, we were not code signing the UITest target. Upon moving to 10.14, running tests now fails with a bootstrap error before the tests even begin to run. I discovered that turning on automatic code-signing for on the tests gets around this problem.

However, this causes a second problem: on the launchedProcess line above, the attempt to spin up the wiremock server will fail with java.lang.RuntimeException: java.net.SocketException: Operation not permitted. If the server is launched (e.g. on the command line) before I execute the tests, everything works fine.

So how can I get myself out of this catch-22? Everything worked well under 10.13. It's unclear to me what code-signing has to do with launching a mock server.


Solution

  • I did not receive a direct answer to my question, but I did find a workaround. Rather than use a TestObserver to start the WireMock server, you can instead use a pre-action on the test.

    To do this, you want to edit the scheme for your UI test project:

    Edit Scheme

    1. Click the disclosure arrow next to Test, then click on Pre-actions
    2. Click the + to add an action script
    3. Use the shell of your choice (I kept the default, /bin/sh)
    4. Under Provide build settings from, choose your test target
    5. Enter a script that spins up your mock server.

    For the script, I used the following code:

    exec > /tmp/preaction-log.txt 2>&1
    
    # Attempt to connect to an existing wiremock server, and exit if we succeed:
    curl http://localhost:3000/__admin/mappings > /dev/null 2>&1 && exit 0 || echo "Attemmpting to spin up a Wiremock Server:"
    
    # No existing server, so spin one up:
    WIREMOCK_DIR=${BUILT_PRODUCTS_DIR}/${EXECUTABLE_NAME}-Runner.app/Contents/PlugIns/${TARGET_NAME}.xctest/Contents/Resources/
    
    /usr/bin/java -jar "${WIREMOCK_DIR}"wiremock-standalone-2.18.0.jar --port 3000 --root-dir "${WIREMOCK_PATH}" &
    

    Here's what the script does:

    1. Logs all output to /tmp/preaction-log.txt, for debugging, as pre-action scripts do not get logged to the build log.
    2. Checks for a running server on the desired port (3000 in our case). I used the same method WireMockClient uses: attempting to access the "mappings" API. If this succeeds, we exit the script.
    3. If we get to this point we can assume we need to spin up a server. So we do, using some XCode environment variables to point to where we store the server executable within our project (if you always have the WireMock client installed on your system(s) you can change the path to whatever you like).

    Now this script will run whenever you run a test case. We still shut down the server at the end of the run within our TestObserver.