Category Archives: Swift

Unit testing with Swift

Swift comes with its own unit testing framework XCTest, let’s take a look at what we need to do to enable unit testing and how we use this library.

Test Package Dependencies

If you created your package via the Wwift package manager your project will be laid out with Sources and Tests folders.

Here’s an example of a library package, notice the .testTarget section

targets: [
   .target(
      name: "MyLib",
      dependencies: []),
   .testTarget(
      name: "MyLibTests",
      dependencies: ["MyLib"]),
]

Creating a test fixture

Next up we need to import the XCTest library and derive our test fixture/class from XCTestCase, for example

final class MyLibTests: XCTestCase {
   // tests go here
}

Creating a test

Tests are written in test methods within the test case subclass. Test methods have no parameters, no return value and the name should begin with lowercase prefix test, so for example

final class MyLibTests: XCTestCase {
   func testIsValid() {
     // test setup and asserts
   }
}

Asserting our tests

The XCTest library prefixes its assertion method with XCT. Hence, we have XCTAssertEqual for example to assert that two values are equal

final class MyLibTests: XCTestCase {
   func testIsValid() {
      let a = 1
      let b = 1
      XCTAssertEqual(a, b)
   }
}

try…catch, but no finally in Swift

My Swift tip of the day…

Swift handles errors (what we would call exceptions in many other languages) using try and catch, but there’s no finally keyword. Instead, we can wrap a closure and pass to the defer function instead. For example we open some resource (file or whatever – we’re assuming this works fine) we create a defer (a bit like using a Disposable), then we useResource which might exception, but defer will now call closeResource for us

let someResource = openResource()
defer { closeResource(someResource) }

do {
   try useResource(someResource)
} catch {
   throw MyError.resoureFailure()
}

Hey Swift, how many Range types do you need ?

I was messing around with the random number generator code for Int and noticed that the API has two Range types, Range and ClosedRange. Upon further investigation I found that Swift supports even more range types.

So currently the range types are Range, ClosedRange, PartialRangeFrom, PartialRangeUpTo, PartialRangeThrough and CountableRange.

Range

The Range type is “a half-open interval from a lower bound up to, but not including, an upper bound”. Which basically means the ..< syntax, i.e.

let range = 1..<100

In other words range is from (and including) 1 to 100 (but not including 100).

We might write a function to handle this using the following syntax

// usage rangeTest.range(1..<10)
func range(_ r: Range<Int>) -> Void {
  print("\(r.lowerBound), \(r.upperBound)")
}

ClosedRange

The ClosedRange type is “an interval from a lower bound up to, and including, an upper bound”. Which basically means the … syntax, i.e.

let range = 1...100

In other words range is from (and including) 1 to 100 (and including 100).

We might write a function to handle this using the following syntax

// rangeTest.range(1...10)
func range(_ r: ClosedRange<Int>) -> Void {
  print("\(r.lowerBound), \(r.upperBound)")
}

PartialRangeFrom

The PartialRangeFrom type is “a partial interval extending upward from a lower bound”. Which uses … syntax, i.e.

let range = 0...

We might write a function to handle this using the following syntax

// rangeTest.range(..<10)
func range(_ r: PartialRangeUpTo<Int>) -> Void {
  print("\(r.lowerBound)")
}

PartialRangeUpTo

The PartialRangeUpTo type is “a partial half-open interval up to, but not including, an upper bound”. Which uses ..< syntax, i.e.

let range = ..<100

We might write a function to handle this using the following syntax

// rangeTest.range(..<10)
func range(_ r: PartialRangeUpTo<Int>) -> Void {
  print("\(r.upperBound)")
}

PartialRangeThrough

The PartialRangeThrough type is “a partial interval up to, and including, an upper bound”. Which uses the … syntax, i.e.

let range = ...100

We might write a function to handle this using the following syntax

// rangeTest.range(...10)
func range(_ r: PartialRangeThrough<Int>) -> Void {
  print("\(r.upperBound)")
}

CountableRange

The CountableRange is just a type alias around a Range

Creating a console app. in Swift (on Linux)(part 2)

In my post Creating a console app. in Swift (on Linux) I demonstrated creating a console app. using Swift. I used top level statement to create the application, but there’s another way.

Note: Those familiar with C# will see how C# now offers both the traditional class based Program as well as top level statement style, well Swift offers similar.

After creating your application using

swift package init --type executable

Create a file in the Sources folder named App.swift or whatever you like except I found that main.swift didn’t seem to work and caused errors such as error: ‘main’ attribute cannot be used in a module that contains top-level code.

Now in your App.swift paste the following

@main
struct App {
    static func main() {
        print("Hello World")
    }
}

Note: The @main attribute is important to mark the type as the main application code.

If you now use the following from the command line, you should see Hello World output.

swift run

Optionals, optionals everywhere

Swift loves it’s optionals (or nullables if you come from a C# background).

Whilst optionals may lead to fewer unhandled null reference exceptions or the likes they also lead to different ways of unwrapping values.

Declaring variables

First off let’s look at declaring a string type, we might write

let s: String = "Hello"

This is not an optional declaration and hence requires an initial value, i.e. remove the = “Hello” and the compiler complains or try to set to nil and the compiler complains – because this variables is expected to be non-nil. If we want to declare a type as possibly being nil then we need to mark it as such using the optional syntax.

Declaring optionals

let s: String? = "Hello"

The ? on a variable/value declares a type of optional string. We can also declare options in a long hand form

let s: String = Optional.some("Hello")

Equally, the following are equivalent

let s1: String?
let s2: String? = Optional.none

Basically we’re saying a “some” value or “none” value or nil may be assigned to this variable.

One thing to note regarding optionals is that to get the actual value assigned, you have to “unwrap” it from the optional. Basically think in terms of your variable is wrapped in an Optional type and to access it, you need to get (unwrap) the value out of that type.

Implicitly unwrapped optional

Interestingly Swift also has implicitly unwrapped optionals. Whereas an optional may have a value, no value or nil, an implicitly unwrapped optional may only contain a value or nil. Here’s the syntax for implicit optionals (note the trailing !)

let s: String!

The general use of an implicitly unwrapped optional is where maybe you declare a value initially but know it will be set to a value before use and will not be nil again.

Obviously we can still check if a value is nil or not, but the compiler will assume you know what you’re doing so if you’re using a value that’s nil the compiler will not warn you.

Note: If you come from a language like C# or Java, then in the example above, just think that this is more like the way you’d declare Strings in those languages and ofcourse you have the same issues regarding ensuring the value is non-nil/null in those languages.

Why not just using implicitly unwrapped optionals as they seem simple? The main reason is the same as why C# introduced nullable reference types. If you want to do things yourself, no problem, but if you want the compiler to highlight potential misused of variables, i.e. have the compiler warn if it looks like you might be using a nil. Then you’re best to only use implicitly unwrapped optionals sparingly and stick to the optional equivalent.

If you’re a little confused by implicitly unwrapped and optionals, let’s look at this code

var s1: String?
var s2: String!

print(s1.length)
print(s2.length)

Neither variable has been set to a value, hence both are none or nil. If we compile this code the Swift compiler will complain with

error: value of optional type ‘String?’ must be unwrapped to refer to member ‘length’ of wrapped base type ‘String’
print(s1.length)

Because the compiler has our back.

Now commenting out print(s1.length) and recompiling, all looks good to the compiler, but ofcourse when we run this code it’ll blow up with an error such as

Unexpectedly found nil while implicitly unwrapping an Optional value

Basically the compiler see’s the implicitly unwrapped optional essentially ignore it as we (the developer) told it that we know what we’re doing.

Unwrapping an optional as part of a conditional test

We’ll often wish to check if an optional is nil or not and then unwrap it to get the actual underlying value. Using the following syntax does both steps in one go

if let optionalValue = optionalValue {
   // do something with the unwrapped optionalValue
}

Swift also supports the guard statement which is basically another condition operator so we can also unwrap in the same way as above, but with a guard, i.e.

guard let optionalValue = optionalValue else {
   // failed to unwrap
   return
}

// do something with the unwrapped optionalValue

Switch

We can use a switch statement on optionals as well, for example

let s: String? = "Hello"
switch s {
   case .some(let s): print("SOME \(s)")
   case .none: print ("NONE")
}

Using map with optionals

We can use map with optionals

let x: String? = "Hello"
let y = x.map { $0 + "World" }
// y is a String?

Unwrapping with !

In some situations you may know an optional value is not nil in which case you simply wish to unwrap the value without any conditional code in place, that’s when you used the ! operator to force unwrap – basically the developer is saying this is a valid value so just unwrap it. Ofcourse, if the developer is wrong then this will have unintended consequences.

The syntax is as follows

let optionalValue: String? = "Hello"

print(optionalValue!)

Optional chaining

We can use the optional chaining ?. operator on types, for example

let optionalValue: String? = "Hello"
print(s?.count)

The nil coalescing operator

Along with the optional chaining operator ?. we also have the nil coalescing operator

let optionalValue: String? = "Hello"
print(optionalValue ?? "NONE")

init?

I’d been wondering why, when creating some types in Swift I was seeing a requirement to unwrap the type. After all, surely if I created a types it’s not nil and hence why does it need to be optional?

Well Swift does actually allow us to declare a type’s init as optional, for example

struct Email {
   var address: String

   init?(address: String) {
      // not really much of a validation step
      // but in the real world we could ensure
      // the address matches an email regex
      guard address.count > 0 else {
         return nil
     }
   }
}

This is pretty useful as it means if we initialize a type with invalid values, we can simple return a nil optional object instead of the alternative of throwing an exception or the need for a two phase initialization.

URLSession.shared.dataTask and HTTP errors such as 404

The URLSession.shared.dataTask completionHandler passes us three pieces of information. Data, the response from an HTTP request and an error.

Transport errors are returned within the error object but HTTP status codes which we’d possible class as errors are supplied in the response object, so I wanted both transport and HTTP status errors to be returned as an Error.

Note: I’m not using URLSession.shared.data as it’s not currently supported on Linux, from what I can tell.

The code below creates a new dataTask method that is async and returns a Result<Data?, Error>. Let’s first look at new dataTask method without the status code handling

private func dataTask(with request: URLRequest) async -> Result<Data?, Error> {
   await withCheckedContinuation { continuation in
      URLSession.shared.dataTask(with: request) { data, response, error in
         if let error = error {
            continuation.resume(returning: .failure(error))
            return
         }
         continuation.resume(returning: .success(data))
    }.resume()
}

So this creates a continuation which is used as a promise/future/task by calling the continuation resume method with either a success or failure.

As already mentioned, this will not return 404’s (for example) as errors. So let’s simply add code to create a failure when certain HTTP status codes appear. We might want to differentiate between the two types of error, i.e. transport layer and HTTP, so first off let’s create our Error subclass, which looks like this

public enum HTTPError: Error {
    case transportError(Error)
    case httpError(Int)
}

This will allow us to pass back the Error from the URLSession.shared.dataTask completion handler for transport errors and the code from the HTTP status for HTTP errors.

Now we’ll change the earlier code to look like this

private func dataTask(with request: URLRequest) async -> Result<Data?, Error> {
   await withCheckedContinuation { continuation in
      URLSession.shared.dataTask(with: request) { data, response, error in
         if let error = error {
            continuation.resume(returning: .failure(HTTPError.transportError(error)))
            return
         }

         let resp = response as! HTTPURLResponse
         let status = resp.statusCode
         guard (200...299).contains(status) else {
            continuation.resume(returning: .failure(HTTPError.httpError(status)))
            return
         }

         continuation.resume(returning: .success(data))
    }.resume()
}

Now we can check the Result<Data?, Error> for the error and see what type of error it is, for the example below I actually throw the error for the caller to catch

let result = await dataTask(url: url!, httpMethod: "POST", httpBody: httpBody)
if case .failure(let error) = result  {
   throw error            
}

Logging in Swift

Ofcourse we can use print statements to output messages but this is pretty basic, a preference is for a logging framework which allows us to log different levels of information, for example debug, trace, info, error etc.

The documentation Generating Log Messages from Your Code shows that for macOS 11 and later to use the Logger structure. We can find a Logger implementation here apple swift-log.

We’ll need to update the Package.swift file in the Package dependencies as per the following

.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),

Obviously change the version in the above to suit the latest available version.

Next, add the following to the (in my case) executableTarget

.product(name: "Logging", package: "swift-log")

To use the library in your code, declare a variable/const, for example

let logger: Logger = Logger(label: "com.putridparrot.EurekaService")

The label is output to the logger to distinguish the components which are logging.

Now to log we simply use

logger.info("Sending data")

The output would look like this

2022-03-05T16:39:52+0000 info com.putridparrot.EurekaService : Sending data

Out of the box the output is streamed to stderr, for example

LoggingSystem.bootstrap(StreamLogHandler.standardError)

Checkout the swift-log repository for more information on alternate LogHandlers.

Interacting with Eureka from Swift 5

I wanted to get my little Swift console application (developed in the post Creating a console app. in Swift (on Linux)) working with Eureka.

So in the last post I outlined the simple steps to get Eureka up and running in docker. Now let’s combine these posts along with the information from my the post Interacting with Eureka via it’s REST operations and create ourselves a Swift Eureka client.

Before I get started, I have not found the generation of JSON from types or for that matter generating the Dictionary required for JSONSerialization as intuitive as C# (for example). I’m not sure if there’s a better way to do these things, but this code works so it’ll do for now. But if you know a better way please use it.

To start with we’re just going to manually generate our JSON string to match the POST request that we need to tell EUREKA that our application is starting

let json: String = "{" +
  "\"instance\": {" +
    "\"hostName\": \"myhost\"," +
    "\"app\": \"SWIFT-EUREKA\"," +
    "\"vipAddress\": \"myservice\"," +
    "\"secureVipAddress\": \"myservice\"," +
    "\"ipAddr\": \"10.0.0.10\"," +
    "\"status\": \"STARTING\"," +
    "\"port\": {\"$\": \"8080\", \"@enabled\": \"true\"}," +
    "\"securePort\": {\"$\": \"8443\", \"@enabled\": \"true\"}," +
    "\"healthCheckUrl\": \"http://myservice:8080/healthcheck\"," +
    "\"statusPageUrl\": \"http://myservice:8080/status\"," +
    "\"homePageUrl\": \"http://myservice:8080\"," +
    "\"dataCenterInfo\": {" +
      "\"@class\": \"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo\", " +
      "\"name\": \"MyOwn\"" +
    "}" +
  "}" +
"}"

Hopefully this is fairly obvious (even with all the escaping characters) what we’re doing, but to use this in JSONSerialization.data method is seems we need to convert this string to a Dictionary or Array, so here’s an extension method to convert to a dictionary

extension String {
  func toDictionary() -> [String:AnyObject]? {
    if let data = self.data(using: .utf8) {
      return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject]
    }
    return nil
  } 
}

There’s zero error handling here – I leave it to the reader to add that.

So using the extension method toDictionary we’ll convert our JSON string into a dictionary which is then passed into the JSONSerialization.data to create a Data object, i.e.

let data = json.toDictionary()
let httpBody = try JSONSerialization.data(withJSONObject: data!, options: [])

Now we’ll create the URL and URLRequest to call the Eureka service

let url = URL(string: "http://192.168.1.1:8761/eureka/apps/swift-eureka")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = httpBody
request.timeoutInterval = 20

The rest of the code is the same as Creating a console app. in Swift (on Linux) but for completeness it’s list below in it’s entirety (don’t forget the extension method toDictionary is in some extension file).

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

let session = URLSession.shared
let semaphore = DispatchSemaphore(value: 0)

let json: String = "{" +
  "\"instance\": {" +
    "\"hostName\": \"myhost\"," +
    "\"app\": \"SWIFT-EUREKA\"," +
    "\"vipAddress\": \"myservice\"," +
    "\"secureVipAddress\": \"myservice\"," +
    "\"ipAddr\": \"10.0.0.10\"," +
    "\"status\": \"DOWN\"," +
    "\"port\": {\"$\": \"8080\", \"@enabled\": \"true\"}," +
    "\"securePort\": {\"$\": \"8443\", \"@enabled\": \"true\"}," +
    "\"healthCheckUrl\": \"http://myservice:8080/healthcheck\"," +
    "\"statusPageUrl\": \"http://myservice:8080/status\"," +
    "\"homePageUrl\": \"http://myservice:8080\"," +
    "\"dataCenterInfo\": {" +
      "\"@class\": \"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo\", " +
      "\"name\": \"MyOwn\"" +
    "}" +
  "}" +
"}"

let data = json.toDictionary()
let httpBody = try? JSONSerialization.data(withJSONObject: data!, options: [])

let url = URL(string: "http://192.168.0.88:8761/eureka/apps/swift-eureka")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = httpBody
request.timeoutInterval = 20

session.dataTask(with: request) { (data, response, error) in
    let result = String.init(data: data!, encoding: .utf8)
    print(result!)
    semaphore.signal()
}.resume()

semaphore.wait()

At this time I haven’t got a great solution for using types to define our request as the request has some invalid name keys, such as $ and @enabled (again there may be a solution which I’ve not come across yet). To give the reader a starting point, here’s the code I have for turning my EurekaRequest type into a JSON string.

I’ve not included the EurekaRequest so this really just shows a way to convert a type to a JSON string.

let instance = EurekaRequest()
let encoder = JSONEncoder();
// required to stop // becoming \/
encoder.outputFormatting = .withoutEscapingSlashes
let jsonData = try encoder.encode(instance)

// convert to a string
var json = String(data: jsonData, encoding: String.Encoding.utf8)!;

Creating a console app. in Swift (on Linux)

The first thing we need to do is use the Swift CLI to generate the project etc. so run

swift package init --type executable

This will generate Sources folder, Tests, the Package.swift and even a README.md and .gitignore.

Now in the previous post we created a simple hello world Vapor REST server, so let’s write the code to call that from our console app.

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

let session = URLSession.shared
let url = URL(string: "http://192.168.1.1:8080")

let semaphore = DispatchSemaphore(value: 0)

session.dataTask(with: url!) { (data, response, error) in
    let result = String.init(data: data!, encoding: .utf8)
    print(result!)
    semaphore.signal()
}.resume()

semaphore.wait()

Let’s break down this code…

The first bit of interest is the import. We need FoundationNetworking for the URLSession.shared within the Linux implementation (if I understand correctly) but not in the Mac OS version (I need to verify that). To allow us to only import FoundationNetworking if required, we use

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

As you’ve probably surmised URLSession is a shared resource, hence the shared singleton property.

Next we need the URL, which is pretty explanatory. Now the URLSession calls are asynchronous, hence if we just let this code run without the semaphore we’ll exit the application before the response from the web service returns.

The resume method is required to start the task (seems the wrong name to me, but there you go). See dataTask.

Finally we wait on the signal from the semaphore. Within the actual dataTask we’re just going to assume there’s data and no error (ofcourse in a real world application you’ll want to add a guard etc. to check for such things and also check the optionals actual have some value before unwrapping).

In this simple example we’ll simply assuming all worked, unwrap the data and print it to the console. Ofcourse the data will be encoded, so we’re need to create a string from the data and the required encoding as per the String.init.

Now just run the command

swift run

If all went well you’ll see the root level response from the web server code we wrote in the last post.