With Xcode 6, Beta 4, we saw the introduction of access modifiers (maybe a better name is visibility modifiers). In essense, they control the visibility of the member throughout the module and outside of the module.
Here they are:
private
specifies an item that is available only within the context of the fileinternal
specifies an item that is available only within the context of the module (e.g. the compiled target)public
specifies an item that is available only both within the module and to all modules that import the given module
My personal opinion here… I'm not sure. I like the simplicity, but I think I'd simply rather have private
and public
. Seems Brent Simmons agrees. Time will tell, as will further Xcode 6 seeds.
The question naturally arrises: how do I test private methods?
Well, we have three basic options:
- You don't and only test their usage through public APIs 2. You need to expose them publicly 3. You need to add the source to your test project so that they are part of the module and make then internal
Option #3 is the way that I'm currently doing it. Is it the best way? Probably not, but it does have the following advantages:
- We can selectively choose while files to add test coverage for
- There is no duplication of code signatures to maintain
- It works under both debug and ship builds
- There is no impact to the public API surface
Getting Started
To get started, simply create a new project that contains both a Swift target and a Swift XCTest target.
Next up, create a new Swift file. I created one called Person.swift
.
I'll use this dummy class implementation:
public class Person {
private var firstName: String
private var lastName: String
init(_ firstName: String, _ lastName: String) {
self.firstName = Person.ensureValidName(firstName)
self.lastName = Person.ensureValidName(lastName)
}
internal class func ensureValidName(name: String) -> String {
if countElements(name) == 0 {
return "<invalid name>"
}
else {
return name
}
}
}
In this contrived example, we want to validate the function ensureValidName
without actually invoking the initializer. Why might we want to do this? Well, simple: this function could be used in many different places in the API. For instance, we could put this in the setter implementation for firstName
and lastName
.
In order to test this method, we need to actually add Person.swift
as a compile target to the XCTest target as well. Once you do that, the following test case will be valid:
import Cocoa
import XCTest
class TestingInternalsTests: XCTestCase {
func testInvalidNameWithEmptyString() {
XCTAssertEqual(Person.ensureValidName(""), "<invalid name>")
}
func testInvalidNameWithNonEmptyString() {
XCTAssertEqual(Person.ensureValidName("David"), "David")
}
}
There are a couple of drawbacks that we get with this approach:
- We really are breaking the semantic rules here with respect to
private
andinternal
; there is no good reason thatinternal
should be used for this function, except to expose it to tests. 2. We are compiling thePerson.swift
into the XCTest target; this could get messy as your project gets larger and larger.
As always, use with caution and with care. Hacks are hacks for a reason.