Archive for the 'Programming' Category

UIViewRepresentable doesn't respect intrinsicContentSize invalidation

This is a quick write-up of an unexpected SwiftUI limitation I encountered recently. In my own searching, I couldn’t find it documented anywhere so I’m hoping this post will help someone in the future.

Problem summary

You can’t wrap a UIView in SwiftUI’s UIViewRepresentable if that UIView changes its intrinsicContentSize based in its frame width, because UIViewRepresentable will ignore invalidateIntrinsicContentSize(). As a result, the UIView will have its height clipped.

To make it a bit more concrete, imagine you have a UIView that draws multiline text that can line wrap. UILabel would be an example. If you were to adjust the label’s frame width, it would reflow the text and the intrinsic content height would change to reflect that.

During SwiftUI layout, what UIViewRepresentable does is ask the UIView for its intrinsic content size before it has set the UIView‘s frame. It then takes the minimum of the available size from the superview and the UIView‘s intrinsicContentSize, and sets that as the UIView‘s frame. This causes the UIView to reflow the text and invalidate its intrinsic content size. UIViewRepresentable ignores this, and as a result the UIView is clipped too short.

You can find a simplified version of this bug using UILabel on Github.

Investigation

I tried many suggestions from Stack Overflow, and override any size or layout related UIView method that I could. None of the suggestions worked, and almost none of the UIView methods were ever called during layout. I felt I was probably missing something obvious, so I filed a Developer Tech Support incident with Apple to get an official answer. My submission is summarized in the Github repo.

Results

Here’s the official response from Apple:

Hello Andy,

Thank you for contacting Apple Developer Technical Support (DTS). We have reviewed your request and have concluded that there is no supported way to achieve the desired functionality given the currently shipping system configurations.

If you would like for Apple to consider adding support for such features in the future, please submit an enhancement request via Feedback Assistant (https://feedbackassistant.apple.com). For more information on Feedback Assistant, please visit https://developer.apple.com/bug-reporting/.

While a Technical Support Incident (TSI) was initially debited from your Apple Developer Program account for this request, we have assigned a replacement incident back to your account.

Best Regards,

Developer Technical Support
Worldwide Developer Relations
Apple, Inc.

According to the response this is expected behavior, so any change would an enhancement request. I have since filed the enhancement request as feedback FB8499811.

Moment of Zen

I have the exact same view on macOS as an NSView wrapped up in an NSViewRepresentable. It works great. When the NSView invalidates its intrinsic size, NSViewRepresentable re-queries the intrinsic size and updates the frame appropriately.

I guess someone already filed that enhancement request.

Sharing HTTP API bindings between Swift Vapor and iOS/macOS apps

I’ve been building a macOS/iOS front for my Swift Vapor side project. When I first started I simply duplicated the API HTTP request and response information between the client and server. But, as the project has grown, this has become more tedious. Since both client and server are in Swift, it seems like I should be able to come up with a more scalable solution. Ideally, I’d like to define an API in one place and have both the client and server code use it. So in this post, I will: figure out what’s shareable, wrestle with package managers to share it, and finally integrate the API component into both the server and client.

What can be shared

I first had to determine what parts of the API made sense to share between the client and server. Both would need the same information, but they encode that information in different ways, and those ways might not be compatible. In my thinking there are five pieces of API information: the URL path, HTTP method, query parameters, request body, and response body.

While it would be nice to share the URL path, the client and server encoded them in very different ways. The client used a raw string for the entire path, while the server implicitly encoded the path, one component at a time, in a declarative router. The HTTP method was treated similarly. The client had an enum for the method, while the server encoded the method implicitly in the routing table. So I couldn’t easily share paths and methods. Or, perhaps more accurately, I didn’t feel I could share them without making the client and server code worse.

Query parameters are special in that they are not declared on either side. The server checks for their existence down in the controller code, but there’s no standard declaration or deserialization of them. So currently, I can’t share them. However, I feel like this could be improved somehow using Codable to define all the possible query parameters. I will likely revisit this in the future.

I found that only the request body and the response bodies were used similarly enough on both sides to be sharable. Both declared them as Codable structs, albeit with slightly different names. But I felt like I could consolidate on a naming scheme that would make sense for both sides.

I also briefly thought about putting the sending and receiving of responses into the shareable library. But that didn’t work easily because it would require importing the Vapor framework for the server support, which I didn’t want to do for a Cocoa app. Perhaps more importantly, that part of the code wouldn’t actually be shared; only one side would be using it. So my decision was to only share data structures and not code.

Structure and naming in the shared component

Now that I had decided that I only wanted to share the request and response data structures, I needed to figure out a way to build that. I had a few requirements. First, I needed to minimize name collisions; the library was to be imported into both the client and server apps, which increased the odds of a collision. Second, I needed to name the data structures in a way that wasn’t client or server centric, which was their current state. The names needed to make sense on both the client and server. Thirdly, I needed to name things consistently so they were discoverable. Finally, there were some functional requirements, such as the requests and responses needed to be Codable and Equatable.

First, to solve global name collisions, I nested everything in a namespace. Since Swift doesn’t have real namespaces, I used an enum:


import Foundation

public enum MyAppAPI {

}

I put the app name in the namespace name because my client app already had a type named API. It also would allow the client to import multiple API bindings in the future without collisions.

As far as structuring the API bindings consistently, I decided to group things by REST resource. As an example, here’s the bindings for the user resource:


import Foundation

public extension MyAppAPI {
    public enum User {
        public struct Envelope<T>: Codable, Equatable where T: Codable, T: Equatable {
            public let user: T

            public init(user: T) {
                self.user = user
            }
        }

        public struct ShowResponse: Codable, Equatable {
            public let id: UUID
            public let email: String

            public init(id: UUID, email: String) {
                self.id = id
                self.email = email
            }
        }

        public struct UpdateRequest: Codable, Equatable {
            public let resetTokens: Bool

            public init(resetTokens: Bool) {
                self.resetTokens = resetTokens
            }
        }
    }
}

There’s a lot to unpack. First, I put each resource into its own file. Second, I created a “namespace” (i.e. a Swift enum) for each resource inside the main API namespace. Inside the resource namespace, I created the individual requests and resource bodies. My naming convention was REST verb + Request or Response. My hope was this would make them easily discoverable. I also used envelopes in my APIs for clarity, so I declared my envelope for each resource as well.

In doing this work, I ran into a few practical considerations, aka things I had to do to appease Swift. The first thing was making everything public, which was a bit tedious. This had a knock-on effect in that the struct default inits didn’t work because they’re implicitly internal. I had to go through and manually implement the inits so that I could declare them public. This was enough overhead that I briefly reconsidered if having a sharable API component was worth doing. In the end I decided it was worth it, but I really wish there was a way to specify the access level of the implicit init on a struct.

One thing I didn’t attempt to deal with was API versioning. That’s only because I haven’t had to deal with it yet. I suspect I’ll add a namespace under the resource namespace for each non-v1 version, and add the updated request and response bodies there.

How to share

I had a plan of what to share, but I needed to figure out the mechanics of how to share. In most languages, this is done by a package manager, and Swift is no different. In fact, for Swift, there’s at least three package managers that can be chosen: Swift Package Manager (SwiftPM), Carthage, and Cocoapods. Ideally, I would like to use only one for both client and server. Unfortunately, each package manager has its own limitations, which made using only one undesirable.

SwiftPM has Linux support, but Carthage and Cocoapods don’t, meaning SwiftPM is the only viable option for my Swift Vapor app. However, SwiftPM doesn’t currently have the ability to generate Cocoa frameworks or apps, only Swift static libraries or command line apps. Since my API component doesn’t have any resources that require a framework, and Xcode can link against static Swift libraries for Cocoa apps, SwiftPM is a technical possibility. However, there is a decent amount of inconvenience involved with this approach (Googling can turn up tutorials on how). One is that my client apps would need two package managers (SwiftPM and either Carthage or Cocoapods). That’s because outside of my shared API component, all of the other packages my client app needs are only available as Carthage or Cocoapods packages. SwiftPM is supposedly going to get the ability to build Cocoa apps and frameworks sometime in the future, but not today.

In the end, I decided to make my API component both a SwiftPM package and a Carthage package. My Swift Vapor app would use the SwiftPM package, and my Cocoa apps would use the Carthage package. Although this meant a higher up front cost of setting up both, my thinking is it shouldn’t noticeably increase the maintenance burden afterwards. The benefit is each app can use the package manager best suited for its platform, which should make updating the API component easier when changes are made.

Between the two package managers, SwiftPM is stricter, requiring a specific directory hierarchy. Since Carthage uses an Xcode project, it could adapt to any hierarchy. For that reason, I tackled setting the SwiftPM package up first.

Refactoring the Swift Vapor app

In my apps, the server app was the source of truth for API information. It had the full definitions of the request and response bodies, and was the most up-to-date. So my plan was to pull the definitions out of the Vapor app into the API component, publish the component, then have the Vapor app import that component. Later, I could figure out how to refactor the Cocoa client apps.

First I created a SwiftPM package for the API component from the command line, and committed it to a git repo.


> mkdir MyAppAPI
> cd MyAppAPI
> swift package init
> git init
> git commit -am "Initial commit"

Next, I moved over the API request and response definitions from the Vapor app, and modified them to fit the structure and naming conventions I defined earlier. I’d use swift build periodically to make sure everything was valid Swift code from SwiftPM’s perspective. Since I prefer Xcode’s editor to a command line editor, I used swift package generate-xcodeproj to create a project, and then made my changes in Xcode. When I was done, I committed my changes in git.


> git commit -am "Adding API definitions"

To share the package, I needed a remote git repo that both client and server could pull from, plus a version number. Since Bitbucket offers free private repos, I created one there, and added it as the remote origin. (Github recently announced free private repos, too.) Then I tagged my repo with a version, and pushed it to the remote:


> git remote add origin <MyAppAPI bitbucket git URL>
> git tag 0.0.1
> git push origin master --tags

Note that SwiftPM version tags are just the version number, sans any prefix or suffix.

I was now ready to refactor my Vapor app to pull in this shared package and use it instead. First, I modified the Package.swift file to include it as a dependency:


    dependencies: [
        ...
        .package(url: "<MyAppAPI bitbucket git URL>", from: "0.0.1"),
        ...
    ],
    ...
    targets: [
        ...
        .target(name: "App", dependencies: [
            ...
            "MyAppAPI",
            ...
            ]),
        ...
    ]

Running vapor update from the command line pulled down the package and updated all my dependencies.

Swift Vapor needs one more thing on the API request and responses before they are useable. Namely, it needs the top-level type of the request and response to conform to the Content protocol that Vapor defines. Fortunately, it’s just an subprotocol of Codable that provides some extra methods, so the requests and responses only need to be declared as conforming. Further, since it’s only the top level types, and I used envelopes to wrap my request and response, for me I only needed to conform the envelopes. I decided to do this all in one file, with the hopes it would be easier to keep up-to-date.


import Foundation
import Vapor
import MyAppAPI

extension MyAppAPI.Session.Envelope: Content {}
extension MyAppAPI.User.Envelope: Content {}
...

This is a bit tedious, but I didn’t find it overly so. In hindsight, using envelopes saved me a bit of work. If I hadn’t used envelopes each individual request and response would have been the top-level type, and I would have had to conform each to Content.

The last piece was to go through all my controllers and have them import MyAppAPI and use the structs defined there.

In doing this part of the work, I learned a couple of things. First, all the namespacing does make using the shared package a bit verbose. Second, separating out the request and response models from the server helped clarify what was a server model and what was a response payload. I felt this work improved the server codebase.

Refactoring the Cocoa/CocoaTouch app

To get my API package working the Cocoa clients, I needed to make the API package a proper Carthage package, then import that into the Cocoa app. Since Carthage works by building an Xcode project, my first job was to create an Xcode project that built both iOS and macOS targets. This was straight forward, although tedious, since the default Xcode templates create a different folder layout than SwiftPM requires.

First, in order to create a Carthage Xcode project, I deleted the existing Xcode project that the SwiftPM had built. It builds a static Swift library, but Carthage needs an iOS/macOS framework. Second, I needed to commit the Xcode project to git for Carthage, but SwiftPM generates a .gitignore file that excludes Xcode projects. So I removed the *.xcodeproject line from .gitignore. Finally, I created an iOS framework project in Xcode with the same name as my package (e.g. MyAppAPI).

While I now had an Xcode project, it was using the Xcode generated files and not the actual files I wanted. Fixing this took several manual steps. First I closed Xcode, then moved the project file itself to the root folder of the package. I moved the framework header (e.g. MyAppAPI.h) and the Info.plist file into the source folder (e.g. Sources/MyAppAPI). Then I deleted all of the other Xcode generated files. Next I opened the project in Xcode. I removed all of the existing file references that were now broken, and then added all of Swift files, the framework header, and the Info.plist to the project. I had to mark the framework header as public, and then go into the Build Settings to update the path to the Info.plist to be correct. At this point I could build an iOS framework.

The second step was to create a macOS framework. It was similarly messy. First, in Xcode, I created a new target for a macOS framework named MyAppAPI-macOS. I went through and deleted all the files Xcode created in performing that, then pointed the macOS target to all the same files that the iOS target had. The Info.plist and framework header were sharable between iOS and macOS. I did have to update the framework header to import Foundation/Foundation.h instead of UIKit/UIKit.h. In the Build Settings, I had to update the path to the Info.plist. Additionally, I needed the iOS and macOS frameworks to have the same name and bundle identifier so the client code could import using the same name. To do that, I modified the Product Name and the Product Bundle Identifier to manually be the same as the iOS target. Building the macOS target now worked for me.

Xcode also defines a test target and files, but since I was only defining types, I didn’t feel I needed unit tests, so I deleted it.

To verify that Carthage would be able to build my project file, from the root of the package I ran:


> carthage build --no-skip-current

When everything successfully built, I was ready to publish my Carthage package.

Carthage is decentralized like SwiftPM, so to publish my package I just needed to tag a version and the push that to the remote git repository. Unfortunately, Carthage and SwiftPM use two different formats for version tags. Carthage prefixes it’s version tags with “v”, while SwiftPM has no prefix. So I committed all my Carthage changes, tagged both Carthage and SwiftPM, then pushed.


> git commit -am "Adding Carthage support"
> git tag 0.0.2
> git tag "v0.0.2"
> git push origin master --tags

Finally, I could pull the shared API package into my Cocoa apps. That involved adding it as a dependency in Carthage and updating. I added the repo to my Cartfile:


git "<MyAppAPI bitbucket git URL>"

Then updated Carthage from the command line.


> carthage update

Carthage doesn’t modify project files (which I consider a feature), so I went and added the MyAppAPI.framework to both my iOS and macOS client app targets.

Using the shared API package was straight forward in the client apps. It was simply importing the MyAppAPI framework, then using the types. I did end up using some typealiases to rename some of the API responses. This was because the client app’s model was the same as the response model. To me this makes sense, because the server is publishing a REST-ful interface, meaning the response model should be the resource model.

And, with that, I was done!

Conclusion

My side project has both server and client apps, all written in Swift. Because they share a common language, I wanted to leverage that to share HTTP API information between the two. My hope was to reduce redundant definitions that could get out of sync. To accomplish this, I created an API repo that contained the API request and response bodies. The repo was both a valid SwiftPM package and a valid Carthage package at the same time. This allowed both the Swift Vapor server and the Cocoa iOS/macOS apps to include the same shared API package.

Handling periodic tasks in Swift Vapor

In my Swift Vapor side project, there have been a couple of instances where I wanted to run some periodic tasks. My needs were pretty simple: every X amount of time, I wanted to run a closure that would perform some database operations. On top of that, it would be nice to have a tiny bit of infrastructure to make periodic jobs require less boilerplate, have visibility in production (i.e. logging if the job was successful or not), and the ability to verify the jobs in integration tests.

In this post, I’m going to flesh out a periodic job interface, lay out some implementation approaches, talk through what I actually did, and finally how I tested it.

A periodic example

First, I’ll define how I’d like to be able to schedule a periodic task in client code. Here’s an except from my app:


func boot(_ app: Application) throws {
    let jobQueue = try app.make(JobQueue.self)
    jobQueue.schedule(initialDelay: .hours(1),
                      delay: .hours(24),
                      withName: "Accounts.cleanup") { container, connection -> Future<Void> in
        return self.cleanup(with: container, on: connection)
    }
}

private func cleanup(with container: Container, on connection: DatabaseConnectable) -> Future<Void> {
    return // task that performs clean up
}

I didn’t need a complicated interface to schedule a periodic job. The initial delay and repeated delay are givens for any periodic task. The name is a bit extra, but it turned out to be quite helpful when testing and logging. Finally, the “job” itself is a closure that’s passed a dependency injection container and a database connection. Those two things are needed for just about any meaningful work in Vapor. Finally, the closure will return a Void promise, which is used by the job infrastructure to know when it is complete, and if it was successful or not.

Although the JobQueue interface is inferable from the example above, here’s exactly how I’ve defined it for my app:


import Foundation
import Vapor

typealias Job = (Container, DatabaseConnectable) -> Future<Void>

protocol JobQueue: Service {
    func schedule(initialDelay: TimeAmount, delay: TimeAmount, withName name: String, _ job: @escaping Job)
}

I’ve defined it as a protocol because I’ll inject a fake implementation in my integration tests. Since I’m taking advantage of the dependency injection system in Vapor, I conform the protocol to Service. But all I needed was one method on the protocol to actually schedule the periodic job. The TimeAmount is a datatype defined down in the NIO framework.

JobQueue Decisions

Now that I’ve defined the JobQueue interface, I need to figure out how to implement it. I thought of a few options.

One option was to register a timer in my app at the given interval, and run the periodic job closure then and there. The benefit of this is it simple to understand and implement. The downside is it doesn’t scale up or down very well. If my host spins up more than one instance of my app, both instances will try to run this periodic task. Or, if I’m on a hobbyist plan, and my host spins down all my instances because of lack of requests, then my periodic task won’t run at all.

Another option was to find or build something like Ruby’s Resque but for Swift, and use a timer to schedule a background job on Resque-but-for-Swift to perform the periodic job. The benefit would be the job would only be run once no matter how many instances of my app were spun up, plus job persistence. The downside is, as far as my Google searches show, such a thing does not exist, and building it myself would be a significant undertaking.

There were also some options that didn’t fit the interface I gave above, but could be legitimate solutions. First up, I could define a Vapor command that’s invokable from my app’s command line that performs the periodic job. Then, using my host’s infrastructure, manually invoke the command when I think it needs to be run. The downside is it relies on me remembering to run the command. However, it wouldn’t need any internal job infrastructure, and would only run on the instance I wanted. A second option would be to do nothing. If my periodic job didn’t run nothing bad would happen for a very long time. The upside to this is it cost nothing to implement. The downside is there is a timebomb in my app that will one day go off. But that might be fine because the app might not last that long, or be completely redesigned so the periodic job is unnecessary.

In the end, I went with the first solution of registering a timer in my app, and running the closure when it triggered. I decided this for a few reasons. First, I’m lazy and forgetful and won’t remember to run some manual command. Second, my side project probably won’t ever scale beyond one instance. Third, even if it did scale up, the periodic tasks running concurrently wouldn’t hurt anything. Finally, this solution did something (so no timebomb), but was the least investment that did something.

As far as using a timer in my Vapor app, my first attempt was to use a 3rd party Swift library that implemented periodic jobs on top of DispatchQueue. That did seem to work, but it happened outside of Vapor’s EventLoops, which always seemed a little janky to me. Later, I discovered that the NIO framework offered RepeatedTasks on their EventLoops, so I used those instead.

Implementing JobQueue

I had an approach now, I just needed to implement it. First up, building the production implementation of JobQueue.


import Foundation
import Vapor

final class ProductionJobQueue: JobQueue {
    private let eventLoop: EventLoop
    private let container: Container
    private let logger: Logger

    init(eventLoop: EventLoop, container: Container, logger: Logger) {
        self.eventLoop = eventLoop
        self.container = container
        self.logger = logger
    }
    ...
}

The ProductionJobQueue needed a few things in order to function. First, it needs an EventLoop so it can schedule RepeatedTasks. Second, it needs a dependency injection Container so it can get a database connection later to hand to a periodic job. Finally, it takes a Logger because the app logs any periodic job invocation to provide some insight into what’s going on in the deployed app.

The only required method of the JobQueue protocol is the schedule() method:


final class ProductionJobQueue: JobQueue {
    ...
    func schedule(initialDelay: TimeAmount, delay: TimeAmount, withName name: String, _ job: @escaping Job) {
        eventLoop.scheduleRepeatedTask(initialDelay: initialDelay, delay: delay) { repeatedTask -> EventLoopFuture<Void> in
            return self.execute(job, withName: name, on: repeatedTask)
        }
    }
    ...
}

This is a thin wrapper around EventLoop.scheduleRepeatedTask(). All the interesting bits of the implementation are in the execute() method which actually calls the job closure.


final class ProductionJobQueue: JobQueue {
    ...
    private func execute(_ job: @escaping Job, withName name: String, on repeatedTask: RepeatedTask) -> Future<Void> {
        let startTime = Date()
        return container.withPooledConnection(to: .psql) { connection -> Future<Void> in
            return job(self.container, connection)
        }.map {
            self.logSuccess(for: name, whichStartedAt: startTime)
        }.catch { error in
            self.logFailure(error, for: name, whichStartedAt: startTime)
        }
    }
    ...
}

The execute() method provides most of the value add functionality of the JobQueue. First off, it grabs a database connection for the job, so the job doesn’t have to itself. Once it has a connection, it invokes the job closure with the container and database connection. It waits on the job to complete, then logs the success or failure of the job.


final class ProductionJobQueue: JobQueue {
    ...
    private func logSuccess(for name: String, whichStartedAt startTime: Date) {
        let time = Date().timeIntervalSince(startTime).asFormattedMilliseconds
        logger.info("JOB \(name) -> SUCCESS [\(time)]")
    }

    private func logFailure(_ error: Error, for name: String, whichStartedAt startTime: Date) {
        let time = Date().timeIntervalSince(startTime).asFormattedMilliseconds
        logger.info("JOB \(name) -> FAILURE [\(time)]")
    }
}

The logging of jobs is simple, but provides some necessary transparency. It just logs the name of the job, whether it was a success or failure, and the time it took to complete. Even though I capture the error in the failure case, I don’t log it for now. That’s because it might possibly have sensitive info in it (like database connection info) that I don’t want being saved out to a log. In the future, I might log out the name of the error type, if I discovered I needed more information when debugging production issues.

The asFormattedMilliseconds property above is an extraction of code from my earlier Swift Vapor logging post:


extension TimeInterval {
    private static let intervalFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.maximumFractionDigits = 2
        formatter.multiplier = 1000
        return formatter
    }()

    var asFormattedMilliseconds: String {
        return TimeInterval.intervalFormatter.string(for: self).map { $0 + "ms" } ?? "???ms"
    }
}

Finally, I needed to implement the Service protocol so the class could be used in the dependency injection system.


extension ProductionJobQueue: ServiceType {
    static var serviceSupports: [Any.Type] {
        return [JobQueue.self]
    }

    static func makeService(for worker: Container) throws -> ProductionJobQueue {
        return try ProductionJobQueue(eventLoop: worker.eventLoop, container: worker, logger: worker.make())
    }
}

As usual, I used ServiceType to conform to the Service protocol. This allowed me to register with only the type, plus have a dependency injection container available when the instance was created. This implementation was a bit different than usual, in that I needed to override the static serviceSupports property to include the JobQueue protocol. This is needed because by default ServiceType only registers the concrete type. By listing JobQueue as supported, client code can ask the container for JobQueue and they’ll get a ProductionJobQueue in the production app. The makeService() method takes full advantage of being given a Container: it passes in the event loop from it, the container itself, and creates a logger from the container.

Finally, ProductionJobQueue is registered as a service from the ProductionConfiguration. At this point, periodic jobs should work in production. But it would be nice to test them.

Testing

I spent some time thinking about what and how to test periodic jobs. I knew I wanted integration level testing, and that I’d facilitate that by building a fake implementation of JobQueue. I needed a fake because waiting on timers to fire in a test isn’t a viable option, especially if they’re only firing once a day. Also, for me, integration testing meant I should be able to verify that the periodic job was both registered and it performed what it was supposed to. I considered two possible solutions.

The first idea was to have a fake job queue that individual tests could manipulate the clock on. That is, a test could say “pretend two hours have elapsed” to the fake job queue, and the job queue would go look through the registered jobs and fire the ones that should have fired by then. The upside to this approach is it could be used to validate the initial and repeating delays given at registration were working. The downside is this would require a sophisticated fake that could do time calculations reliably. This would give me less confidence that I was testing production code, as opposed to testing my fake job queue.

The second idea was to require each periodic job to provide a unique name. Then individual tests could ask the fake job queue to invoke a periodic job by that name. The upside is the fake is simple and predictable. The downside is jobs have to have globally unique names, which is a bit of overhead.

I decided on the second option to invoke jobs by name. Because of the logging done in production, the requirement that jobs have globally unique names was already there, so the downside was moot. Additionally, the “upside” of the first option was validating the delay values. But in my case, they were always constants, so there seemed to be little value in validating them. Finally, the ease of implementing the second option was hard to ignore.

This approach turned out to be easy to use in my tests. As an example:


func testRemoveExpiredChallenges() throws {
    // setup

    try app.jobQueue.execute("Accounts.cleanup", container: app.container, connection: connection)

    // validate here
}

I ended up redacting all the setup and validation code for this test for succinctness, and because all of it was specific to the app and not that interesting. The only part left is invoking the periodic job on the fake job queue, which is done by passing the job name, a Container and a connection to the database. The app seen here is a TestApplication, which I covered in a post about integration testing Swift Vapor.

Because of my selected testing approach, TestJobQueue was easy to implement. Here’s how I conformed it to the JobQueue protocol:


import Foundation
import Vapor

final class TestJobQueue: JobQueue {
    struct Entry {
        let initialDelay: TimeAmount
        let delay: TimeAmount
        let name: String
        let job: Job
    }

    var schedule_wasCalled = false
    var schedule_wasCalled_withEntries = [Entry]()
    func schedule(initialDelay: TimeAmount, delay: TimeAmount, withName name: String, _ job: @escaping Job) {
        schedule_wasCalled = true
        let entry = Entry(initialDelay: initialDelay, delay: delay, name: name, job: job)
        schedule_wasCalled_withEntries.append(entry)
    }
    ...
}

The schedule() method is just a recorder. It creates an entry for each scheduled job and stores it in an array. The important bits that are used later are the name and job. I ended up storing the initialDelay and delay so if a test actually wanted to validate them, they could.

The other interesting piece to the TestJobQueue class is the execute() method that tests call when they want to fake a periodic job being triggered.


final class TestJobQueue: JobQueue {
    ...
    func execute(_ name: String, container: Container, connection: DatabaseConnectable) throws {
        guard let entry = schedule_wasCalled_withEntries.first(where: { $0.name == name }) else {
            return
        }

        try entry.job(container, connection).wait()
    }
}

My implementation is as simple as I could make it. It searches the array of entries looking for the first job with the matching name. If it can’t find a matching job, it silently fails. This is intentional: it mimics what would happen in production if a periodic job wasn’t properly registered. Namely, nothing would happen. It is up to the individual test cases to verify that a job’s side effects are happening. However, execute() does wait on the job to complete before returning, just to make the tests simpler.

The last bit needed for TestJobQueue is registering it with the dependency injection system. I did it differently from the production job queue, however. ProductionJobQueue conformed to ServiceType because it needed access to a dependency injection container to initialize. TestJobQueue doesn’t have the same requirement to instantiate. Further, it would be useful to the tests if the TestJobQueue were exposed to them as that type — so they can access the execute() method. Otherwise they’d have to force cast a JobQueue to a TestJobQueue, and I find that more than a little gross.

For that reason, I modified TestingConfiguration to hold an exact instance:


struct TestingConfiguration: ConfigurationType {
    ...
    let jobQueue = TestJobQueue()

    func configure(_ services: inout Services) throws {
        ...
        services.register(jobQueue, as: JobQueue.self)
        ...
    }
    ...
}

This is the TestingConfiguration that I described in how I do environment configuration for Swift Vapor. Here, it registered a member variable of type TestJobQueue as a JobQueue. By making it publicly available on TestingConfiguration, the TestApplication has easy access to it.

The final piece is exposing the TestJobQueue on TestApplication so it’s convenient for tests to use.


class TestApplication {
    ...
    var jobQueue: TestJobQueue {
        return configuration.jobQueue
    }
    ...
}

Since the TestApplication already has a reference to TestingConfiguration, it can return that instance. The test code now works.

Conclusion

My Swift Vapor app had a need to run some clean up jobs periodically. The requirements were simple: it was ok if the job ran on multiple instances or not at all, but it should make a good faith effort to run at least once. I wanted a class that reduced the necessary boilerplate for scheduling a job, while adding logging to the jobs, and being testable. I settled on NIO‘s RepeatedTasks to implement timers to run the periodic tasks in each app instance. For testing, I kept the testing fake simple: the tests could invoke jobs by name, and then validate the side effects.