Section 10/121 menit
10. Real Use Cases
10. Real Use Cases
Use Case 1: Testing Validation Layer
swift
import Testing
@Suite("User Registration Validation")
struct RegistrationValidationTests {
let validator = RegistrationValidator()
// Grup: valid inputs
@Suite("Valid Inputs")
struct ValidInputs {
let validator = RegistrationValidator()
@Test("Username valid", arguments: [
"ari_supriatna",
"budi123",
"user-name",
"a", // minimum 1 karakter
String(repeating: "a", count: 30) // maximum
])
func validUsernames(username: String) throws {
#expect(throws: Never.self) {
try validator.validateUsername(username)
}
}
}
// Grup: invalid inputs dengan pesan error spesifik
@Suite("Invalid Inputs")
struct InvalidInputs {
let validator = RegistrationValidator()
@Test("Username terlalu panjang")
func usernameTooLong() throws {
let longUsername = String(repeating: "a", count: 31)
#expect(throws: ValidationError.tooLong) {
try validator.validateUsername(longUsername)
}
}
@Test("Karakter tidak valid dalam username", arguments: [
"user name", // spasi
"user@name", // @
"user.name!", // !
])
func usernameInvalidCharacters(username: String) {
#expect(throws: ValidationError.invalidCharacters) {
try validator.validateUsername(username)
}
}
}
// Test integrasi: seluruh form
@Test("Form valid diterima")
func validFormAccepted() throws {
let form = RegistrationForm(
username: "ari123",
email: "ari@example.com",
password: "SecurePass123!"
)
try validator.validate(form) // tidak throws = sukses
}
@Test("Form invalid ditolak dengan semua errors", .tags(.regression))
func invalidFormReturnsAllErrors() {
let form = RegistrationForm(username: "", email: "invalid", password: "123")
let errors = validator.collectErrors(form)
#expect(errors.contains { $0 == .requiredField("username") })
#expect(errors.contains { $0 == .invalidFormat("email") })
#expect(errors.contains { $0 == .tooShort(field: "password", minimum: 8, actual: 3) })
#expect(errors.count == 3)
}
}
Use Case 2: Testing Networking Layer
swift
import Testing
// Mock untuk testing
actor MockHTTPClient {
var responses: [URL: Result<Data, Error>] = [:]
var requestLog: [(url: URL, method: String)] = []
func stub(url: URL, response: Result<Data, Error>) {
responses[url] = response
}
func request(url: URL, method: String) async throws -> Data {
requestLog.append((url: url, method: method))
switch responses[url] {
case .success(let data): return data
case .failure(let error): throw error
case .none: throw URLError(.badURL)
}
}
}
@Suite("User API Client", .serialized)
struct UserAPIClientTests {
let mockClient: MockHTTPClient
let apiClient: UserAPIClient
init() async {
mockClient = MockHTTPClient()
apiClient = UserAPIClient(httpClient: mockClient)
}
@Test("Fetch user sukses")
func fetchUserSuccess() async throws {
let userData = """
{"id": "123", "name": "Ari Supriatna", "email": "ari@example.com"}
""".data(using: .utf8)!
await mockClient.stub(
url: URL(string: "https://api.example.com/users/123")!,
response: .success(userData)
)
let user = try await apiClient.fetchUser(id: "123")
#expect(user.id == "123")
#expect(user.name == "Ari Supriatna")
#expect(user.email == "ari@example.com")
}
@Test("Fetch user not found returns error")
func fetchUserNotFound() async throws {
await mockClient.stub(
url: URL(string: "https://api.example.com/users/999")!,
response: .failure(URLError(.fileDoesNotExist))
)
await #expect(throws: UserAPIError.notFound) {
try await apiClient.fetchUser(id: "999")
}
}
@Test("Retry on server error", .timeLimit(.seconds(10)))
func retryOnServerError() async throws {
var callCount = 0
// Pertama dua kali gagal, ketiga sukses
await mockClient.stub(
url: URL(string: "https://api.example.com/users/123")!,
response: .failure(URLError(.timedOut))
)
// Test retry behavior
await #expect(throws: Never.self) {
try await apiClient.fetchUserWithRetry(id: "123", maxRetries: 3)
}
}
@Test("Concurrent requests tidak interferensi", .tags(.critical))
func concurrentRequestsIndependent() async throws {
let userIDs = ["1", "2", "3", "4", "5"]
for id in userIDs {
let data = """{"id": "\(id)", "name": "User \(id)", "email": "\(id)@test.com"}"""
.data(using: .utf8)!
await mockClient.stub(
url: URL(string: "https://api.example.com/users/\(id)")!,
response: .success(data)
)
}
// Fetch semua secara concurrent
let users = try await withThrowingTaskGroup(of: User.self) { group in
for id in userIDs {
group.addTask { try await self.apiClient.fetchUser(id: id) }
}
return try await group.reduce(into: []) { $0.append($1) }
}
#expect(users.count == userIDs.count)
#expect(Set(users.map { $0.id }) == Set(userIDs))
}
}
Use Case 3: Testing Actor dan Concurrency
swift
import Testing
@Suite("Cache Actor")
struct CacheActorTests {
@Test("Set dan get value")
func setAndGetValue() async {
let cache = Cache<String, Int>(ttl: .seconds(60))
await cache.set(42, for: "answer")
let retrieved = await cache.get(for: "answer")
#expect(retrieved == 42)
}
@Test("Cache miss returns nil")
func cacheMissReturnsNil() async {
let cache = Cache<String, Int>(ttl: .seconds(60))
let value = await cache.get(for: "nonexistent")
#expect(value == nil)
}
@Test("TTL expiry — entry kadaluarsa")
func ttlExpiry() async throws {
let cache = Cache<String, Int>(ttl: .milliseconds(100))
await cache.set(42, for: "key")
try await Task.sleep(for: .milliseconds(150))
let value = await cache.get(for: "key")
#expect(value == nil, "Entry harus sudah kadaluarsa")
}
@Test("Concurrent writes tidak corrupt state", .tags(.critical))
func concurrentWritesSafe() async {
let cache = Cache<String, Int>(maxSize: 1000, ttl: .seconds(60))
await withTaskGroup(of: Void.self) { group in
for i in 0..<100 {
group.addTask {
await cache.set(i, for: "key_\(i)")
}
}
}
// Verifikasi semua values tersimpan dengan benar
var successCount = 0
for i in 0..<100 {
if await cache.get(for: "key_\(i)") == i {
successCount += 1
}
}
#expect(successCount == 100)
}
// Parameterized test untuk TTL values
@Test("Max size enforcement", arguments: [10, 50, 100, 500])
func maxSizeEnforcement(maxSize: Int) async {
let cache = Cache<Int, Int>(maxSize: maxSize, ttl: .seconds(60))
for i in 0..<(maxSize + 50) {
await cache.set(i, for: i)
}
// Cache tidak boleh melebihi maxSize
let count = await cache.count
#expect(count <= maxSize)
}
}