Documentation/en-us/ArrangeActAssert.md
Whether you're using XCTest, Quick, or another testing framework, you can write effective unit tests by following a simple pattern:
For example, let's look at a simple class called Banana:
// Banana/Banana.swift
/** A delicious banana. Tastes better if you peel it first. */
public class Banana {
private var isPeeled = false
/** Peels the banana. */
public func peel() {
isPeeled = true
}
/** You shouldn't eat a banana unless it's been peeled. */
public var isEdible: Bool {
return isPeeled
}
}
Let's verify the Banana.peel() method does what it's supposed to:
// BananaTests/BananaTests.swift
class BananaTests: XCTestCase {
func testPeel() {
// Arrange: Create the banana we'll be peeling.
let banana = Banana()
// Act: Peel the banana.
banana.peel()
// Assert: Verify that the banana is now edible.
XCTAssertTrue(banana.isEdible)
}
}
Our testPeel() makes sure that, if the Banana.peel() method ever
stops working right, we'll know. This usually happens when our application
code changes, which either means:
If our tests start breaking, how do we know which one of these cases applies? It might surprise you that the name of the test is our best indication. Good test names:
Is our testPeel() method clearly named? Let's make it clearer:
// BananaTests.swift
-func testPeel() {
+func testPeel_makesTheBananaEdible() {
// Arrange: Create the banana we'll be peeling.
let banana = Banana()
// Act: Peel the banana.
banana.peel()
// Assert: Verify that the banana is now edible.
XCTAssertTrue(banana.isEdible)
}
The new name:
testPeel indicates it's the Banana.peel() method.makesTheBananaEdible indicates the
banana is edible once the method has been called.Let's say we want to offer people bananas, using a function called offer():
// Banana/Offer.swift
/** Given a banana, returns a string that can be used to offer someone the banana. */
public func offer(banana: Banana) -> String {
if banana.isEdible {
return "Hey, want a banana?"
} else {
return "Hey, want me to peel this banana for you?"
}
}
Our application code does one of two things:
Let's write tests for these two cases:
// BananaTests/OfferTests.swift
class OfferTests: XCTestCase {
func testOffer_whenTheBananaIsPeeled_offersTheBanana() {
// Arrange: Create a banana and peel it.
let banana = Banana()
banana.peel()
// Act: Create the string used to offer the banana.
let message = offer(banana)
// Assert: Verify it's the right string.
XCTAssertEqual(message, "Hey, want a banana?")
}
func testOffer_whenTheBananaIsntPeeled_offersToPeelTheBanana() {
// Arrange: Create a banana.
let banana = Banana()
// Act: Create the string used to offer the banana.
let message = offer(banana)
// Assert: Verify it's the right string.
XCTAssertEqual(message, "Hey, want me to peel this banana for you?")
}
}
Our test names clearly indicate the conditions under which our tests should pass:
in the case that whenTheBananaIsPeeled, offer() should offersTheBanana. And if
the banana isn't peeled? Well, we have a test for that, too!
Notice that we have one test per if statement in our application code.
This is a great pattern when writing tests: it makes sure every set of conditions
is tested. If one of those conditions no longer works, or needs to be changed, we'll know
exactly which test needs to be looked at.
XCTestCase.setUp()Both of our OfferTests tests contain the same "Arrange" code: they both
create a banana. We should move that code into a single place. Why?
Banana initializer, we'll have to change every test that creates a banana.Let's move the Banana initialization into the XCTestCase.setUp() method, which is called
once before every test method.
// OfferTests.swift
class OfferTests: XCTestCase {
+ var banana: Banana!
+
+ override func setUp() {
+ super.setUp()
+ banana = Banana()
+ }
+
func testOffer_whenTheBananaIsPeeled_offersTheBanana() {
- // Arrange: Create a banana and peel it.
- let banana = Banana()
+ // Arrange: Peel the banana.
banana.peel()
// Act: Create the string used to offer the banana.
let message = offer(banana)
// Assert: Verify it's the right string.
XCTAssertEqual(message, "Hey, want a banana?")
}
func testOffer_whenTheBananaIsntPeeled_offersToPeelTheBanana() {
- // Arrange: Create a banana.
- let banana = Banana()
-
// Act: Create the string used to offer the banana.
let message = offer(banana)
// Assert: Verify it's the right string.
XCTAssertEqual(message, "Hey, want me to peel this banana for you?")
}
}
If you find yourself using the same "arrange" steps across multiple tests, you may want to define a helper function within your test target:
// BananaTests/BananaHelpers.swift
internal func createNewPeeledBanana() -> Banana {
let banana = Banana()
banana.peel()
return banana
}
Use a function to define your helpers: functions can't be subclassed, nor can they retain any state. Subclassing and mutable state can make your tests harder to read.