mirror of
https://github.com/Dimillian/Skills.git
synced 2026-03-25 08:55:54 +00:00
Add swift-testing skill with framework basics
This commit is contained in:
parent
6fe317b4d3
commit
7cd86cf0eb
2 changed files with 282 additions and 0 deletions
127
swift-testing/SKILL.md
Normal file
127
swift-testing/SKILL.md
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
---
|
||||
name: swift-testing
|
||||
description: Swift Testing framework (@Test, @Suite, #expect, #require) for Swift 5.9+/Xcode 16+. Use when writing tests, migrating from XCTest, or fixing Swift Testing failures.
|
||||
---
|
||||
|
||||
# Swift Testing
|
||||
|
||||
## Overview
|
||||
|
||||
Write unit tests using Apple's Swift Testing framework with `@Test`, `@Suite`, `#expect`, and `#require`. Replaces XCTest for non-UI tests.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| XCTest | Swift Testing |
|
||||
|--------|---------------|
|
||||
| `import XCTest` | `import Testing` |
|
||||
| `class FooTests: XCTestCase` | `@Suite struct FooTests` |
|
||||
| `func testBar()` | `@Test func bar()` |
|
||||
| `XCTAssertEqual(a, b)` | `#expect(a == b)` |
|
||||
| `XCTAssertNil(x)` | `#expect(x == nil)` |
|
||||
| `XCTAssertThrowsError` | `#expect(throws:)` |
|
||||
| `XCTUnwrap(x)` | `try #require(x)` |
|
||||
| `setUpWithError()` | `init() throws` |
|
||||
| `tearDown()` | `deinit` |
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Set up the test suite
|
||||
|
||||
Create a struct with `@Suite` and use `init()` for setup:
|
||||
|
||||
```swift
|
||||
import Testing
|
||||
|
||||
@Suite struct UserServiceTests {
|
||||
let sut: UserService
|
||||
let mockRepo: MockRepository
|
||||
|
||||
init() {
|
||||
mockRepo = MockRepository()
|
||||
sut = UserService(repository: mockRepo)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Write test functions
|
||||
|
||||
Add `@Test` to test functions. Remove the `test` prefix:
|
||||
|
||||
```swift
|
||||
@Test func fetchUser_returnsValidUser() async throws {
|
||||
let user = try await sut.fetchUser(id: "123")
|
||||
#expect(user.name == "John")
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use assertions
|
||||
|
||||
- `#expect(condition)` — soft assertion, test continues on failure
|
||||
- `try #require(optional)` — hard assertion, test stops if nil
|
||||
|
||||
```swift
|
||||
#expect(result == expected)
|
||||
#expect(isValid)
|
||||
let value = try #require(optional)
|
||||
|
||||
#expect(throws: ValidationError.self) {
|
||||
try validate(badInput)
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add parameterized tests
|
||||
|
||||
Run the same test with multiple inputs:
|
||||
|
||||
```swift
|
||||
@Test(arguments: ["", " ", " "])
|
||||
func validate_rejectsBlankStrings(_ input: String) {
|
||||
#expect(!isValid(input))
|
||||
}
|
||||
|
||||
@Test(arguments: [
|
||||
(input: "hello", expected: 5),
|
||||
(input: "", expected: 0)
|
||||
])
|
||||
func count_returnsCorrectLength(input: String, expected: Int) {
|
||||
#expect(input.count == expected)
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Apply traits for test control
|
||||
|
||||
```swift
|
||||
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil))
|
||||
func onlyOnCI() { }
|
||||
|
||||
@Test(.disabled("Waiting for backend fix"))
|
||||
func brokenEndpoint() { }
|
||||
|
||||
@Test(.tags(.slow, .network))
|
||||
func networkHeavyTest() { }
|
||||
|
||||
@Test(.timeLimit(.minutes(1)))
|
||||
func mustCompleteFast() { }
|
||||
|
||||
@Suite(.serialized)
|
||||
struct DatabaseTests {
|
||||
@Test func insert() { }
|
||||
@Test func delete() { }
|
||||
}
|
||||
```
|
||||
|
||||
## Common mistakes
|
||||
|
||||
| Mistake | Fix |
|
||||
|---------|-----|
|
||||
| `#expect(x == nil)` on `T??` | Use `try #require(x)` first to unwrap outer optional |
|
||||
| Test not running | Ensure `@Test` attribute is present |
|
||||
| Shared state between tests | Use `init()` for setup, avoid `static var` |
|
||||
| Async test not awaited | Mark test `async`, use `await` |
|
||||
| XCTest assertions in Swift Testing | Replace with `#expect` / `#require` |
|
||||
|
||||
## Reference material
|
||||
|
||||
- See `references/swift-testing-basics.md` for core concepts and syntax.
|
||||
- See `references/migration-from-xctest.md` for migration strategies.
|
||||
- See `references/advanced-patterns.md` for parameterized tests, traits, and mocking.
|
||||
155
swift-testing/references/swift-testing-basics.md
Normal file
155
swift-testing/references/swift-testing-basics.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
# Swift Testing Basics
|
||||
|
||||
## Core Components
|
||||
|
||||
### @Test
|
||||
|
||||
```swift
|
||||
@Test func userCanLogin() { }
|
||||
@Test func emptyInput_returnsError() { }
|
||||
@Test("User can update their profile") func updateProfile() { }
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
- Must be a function (not a property or subscript)
|
||||
- Can be `async` and/or `throws`
|
||||
- Can be instance method or free function
|
||||
- No parameters unless using parameterized testing
|
||||
|
||||
### @Suite
|
||||
|
||||
```swift
|
||||
@Suite struct AuthenticationTests {
|
||||
@Test func login() { }
|
||||
@Test func logout() { }
|
||||
}
|
||||
```
|
||||
|
||||
**Suite features:**
|
||||
- Can be `struct`, `class`, `actor`, or `enum`
|
||||
- `struct` recommended (value semantics, fresh instance per test)
|
||||
- Can nest suites for hierarchy
|
||||
- Supports traits for suite-wide configuration
|
||||
|
||||
### #expect
|
||||
|
||||
```swift
|
||||
#expect(value == 42)
|
||||
#expect(array.isEmpty)
|
||||
#expect(string.contains("hello"))
|
||||
#expect(!isDisabled)
|
||||
```
|
||||
|
||||
Soft assertion—test continues on failure.
|
||||
|
||||
**Comparisons:**
|
||||
```swift
|
||||
#expect(a == b)
|
||||
#expect(a != b)
|
||||
#expect(a > b)
|
||||
#expect(a === b)
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
```swift
|
||||
#expect(throws: (any Error).self) {
|
||||
try riskyOperation()
|
||||
}
|
||||
|
||||
#expect(throws: NetworkError.self) {
|
||||
try fetch()
|
||||
}
|
||||
|
||||
#expect {
|
||||
try validate("")
|
||||
} throws: { error in
|
||||
error as? ValidationError == .empty
|
||||
}
|
||||
|
||||
#expect(throws: Never.self) {
|
||||
try safeOperation()
|
||||
}
|
||||
```
|
||||
|
||||
### #require
|
||||
|
||||
Hard assertion—test stops on failure. Use for unwrapping.
|
||||
|
||||
```swift
|
||||
let user = try #require(await fetchUser())
|
||||
#expect(user.name == "John")
|
||||
|
||||
try #require(array.count > 0)
|
||||
let first = array[0]
|
||||
|
||||
let viewModel = try #require(controller.viewModel as? ProfileViewModel)
|
||||
```
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```swift
|
||||
@Suite struct ServiceTests {
|
||||
let service: MyService
|
||||
let mockRepository: MockRepository
|
||||
|
||||
init() {
|
||||
mockRepository = MockRepository()
|
||||
service = MyService(repository: mockRepository)
|
||||
}
|
||||
|
||||
@Test func fetchData() async { }
|
||||
}
|
||||
```
|
||||
|
||||
`init()` runs before each test. Each test gets a fresh instance.
|
||||
|
||||
### Teardown (class only)
|
||||
|
||||
```swift
|
||||
@Suite class ResourceTests {
|
||||
var tempFile: URL?
|
||||
|
||||
init() throws {
|
||||
tempFile = try createTempFile()
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let tempFile {
|
||||
try? FileManager.default.removeItem(at: tempFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Async
|
||||
|
||||
```swift
|
||||
@Test func asyncFetch() async throws {
|
||||
let data = try await api.fetchData()
|
||||
#expect(data.count > 0)
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
@Test(.timeLimit(.seconds(5)))
|
||||
func mustCompleteFast() async { }
|
||||
|
||||
@Suite(.serialized)
|
||||
struct OrderDependentTests {
|
||||
@Test func first() { }
|
||||
@Test func second() { }
|
||||
}
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
**Xcode:** Cmd+U (all), click diamond (individual)
|
||||
|
||||
**Command line:**
|
||||
```bash
|
||||
swift test
|
||||
swift test --filter UserTests
|
||||
swift test --filter .tags:slow
|
||||
swift test --skip .tags:slow
|
||||
```
|
||||
|
||||
Loading…
Reference in a new issue