Unit Testing with XCTest
Tests prove your code works and keep it working as you change it. You'll write XCTestCase tests, assert with XCTAssertEqual , use setUp / tearDown , test async code, mock with protocols, and meet the new Swift Testing framework.
Learn Unit Testing with XCTest in our free Swift course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Swift course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • Write tests in an XCTestCase subclass
- • Verify results with XCTAssertEqual and friends
- • Prepare and clean state with setUp / tearDown
- • Test async functions with await
- • Mock dependencies by coding to a protocol
- • Meet the new Swift Testing framework ( @Test , #expect )
📊 Common Assertions
Assertion
Passes when
XCTAssertEqual(a, b)
a equals b
XCTAssertTrue(x)
x is true
XCTAssertFalse(x)
x is false
XCTAssertNil(x)
x is nil
XCTUnwrap(x)
x is non-nil (returns it)
1️⃣ Your First Test
Subclass XCTestCase , write methods that start with test , and assert the result. Xcode finds and runs them automatically.
2️⃣ setUp and tearDown
setUp() runs before each test to build fresh state; tearDown() runs after to clean it up. This keeps every test isolated and repeatable.
Your turn. Fill in the base class, the method prefix, and the assertion.
3️⃣ Async Tests and Mocking
Mark a test method async to await async code directly. Coding to a protocol lets you swap in a fast, offline mock during tests.
Now you try. Fill in the keywords that make a test async-aware.
4️⃣ The New Swift Testing Framework
Apple's newer Swift Testing framework uses the @Test macro and #expect instead of XCTest's class and assertions. It reads more naturally and coexists with XCTest.
Pro Tips
- 💡 One behaviour per test. A focused test points straight at the broken thing when it fails.
- 💡 Inject dependencies via protocols so you can mock the network, clock, or database.
- 💡 Keep tests fast and offline. Real network calls make tests flaky and slow.
- 💡 Name tests for behaviour, e.g. testDivideByZeroReturnsNil .
Common Errors (and the fix)
- Test never runs — its method name doesn't start with test .
- Tests interfere with each other — reset shared state in setUp / tearDown .
- "async" call not awaited — mark the test method async and use await .
- Flaky tests — you're hitting the real network; inject a mock instead.
📋 Quick Reference
Task
Code
Notes
Test class
class T: XCTestCase
subclass
Test method
func testThing()
test* prefix
Equality
a == b
Setup
override func setUp()
before each
Async
func testX() async
use await
Swift Testing
@Test func x() { #expect(…) }
newer API
Frequently Asked Questions
Mini-Challenge: Test a Calculator
Write a small test class with setUp, an equality test, and a nil test for a divide function.
🎉 Lesson Complete!
- ✅ Tests live in an XCTestCase subclass; methods start with test
- ✅ Verify with XCTAssertEqual , XCTAssertTrue , XCTAssertNil
- ✅ setUp / tearDown keep tests isolated
- ✅ Mark a test async to test async code; mock via protocols
- ✅ Swift Testing's @Test and #expect are the modern alternative
- ✅ Next lesson: Swift Package Manager
Practice quiz
Which base class do XCTest test classes inherit from?
- TestCase
- XCTestCase
- UITest
- XCTAssert
Answer: XCTestCase. Test classes subclass XCTestCase in the XCTest framework.
Which assertion checks two values are equal?
- XCTAssertTrue
- XCTAssertNil
- XCTAssertEqual
- XCTFail
Answer: XCTAssertEqual. XCTAssertEqual(a, b) fails the test when a and b differ.
By convention, an XCTest test method name must start with...
- check
- test
- spec
- it
Answer: test. Methods beginning with 'test' are discovered and run automatically.
When does setUp() run?
- Before each test method
- Once for the whole class
- After all tests
- Only on failure
Answer: Before each test method. setUp() runs before every test method to prepare fresh state.
When does tearDown() run?
- Before each test
- Never
- Only once at launch
- After each test method
Answer: After each test method. tearDown() runs after each test to clean up.
How do you test an async function in XCTest?
- You cannot
- Mark the test method async and use await
- Use a Timer
- Use DispatchQueue.main.sync
Answer: Mark the test method async and use await. Modern XCTest lets a test method be async so it can await directly.
Why depend on a protocol to enable mocking?
- It runs faster
- It avoids imports
- You can substitute a fake conforming type in tests
- It removes assertions
Answer: You can substitute a fake conforming type in tests. Coding to a protocol lets you inject a mock implementation during tests.
Which assertion fails when a condition is false?
- XCTAssertTrue
- XCTAssertFalse
- XCTAssertNil
- XCTUnwrap
Answer: XCTAssertTrue. XCTAssertTrue fails if the expression evaluates to false.
What is Apple's newer testing framework that uses #expect and @Test?
- Quick
- Nimble
- SnapshotTesting
- Swift Testing
Answer: Swift Testing. Swift Testing is Apple's modern framework using the @Test macro and #expect.
A good unit test should be...
- Slow and order-dependent
- Fast, isolated, and repeatable
- Reliant on the network
- Random in outcome
Answer: Fast, isolated, and repeatable. Unit tests should be fast, isolated, and deterministic.
Continue this course
- Previous: SwiftData Basics
- Next: Swift Package Manager