xiaohongshu
xiaohongshu
ios-unit-test
Эксперт iOS тестирования. Используй для XCTest, UI testing и iOS test patterns.
Full skill instructions
Expert in iOS testing with XCTest framework and best practices.
import XCTest
@testable import YourApp
class UserServiceTests: XCTestCase {
// System Under Test
var sut: UserService!
var mockNetworkManager: MockNetworkManager!
override func setUpWithError() throws {
try super.setUpWithError()
mockNetworkManager = MockNetworkManager()
sut = UserService(networkManager: mockNetworkManager)
}
override func tearDownWithError() throws {
sut = nil
mockNetworkManager = nil
try super.tearDownWithError()
}
// MARK: - fetchUser Tests
func test_fetchUser_withValidId_returnsUser() async throws {
// Arrange
let expectedUser = User(id: "123", name: "John Doe")
mockNetworkManager.fetchUserResult = .success(expectedUser)
// Act
let result = try await sut.fetchUser(id: "123")
// Assert
XCTAssertEqual(result.id, expectedUser.id)
XCTAssertEqual(result.name, expectedUser.name)
XCTAssertEqual(mockNetworkManager.fetchUserCallCount, 1)
XCTAssertEqual(mockNetworkManager.lastFetchedUserId, "123")
}
func test_fetchUser_withInvalidId_throwsError() async {
// Arrange
mockNetworkManager.fetchUserResult = .failure(NetworkError.notFound)
// Act & Assert
do {
_ = try await sut.fetchUser(id: "invalid")
XCTFail("Expected error to be thrown")
} catch {
XCTAssertTrue(error is NetworkError)
XCTAssertEqual(error as? NetworkError, .notFound)
}
}
}
// Protocol definition
protocol NetworkManagerProtocol {
func fetchUser(id: String) async throws -> User
func saveUser(_ user: User) async throws
}
// Mock implementation
class MockNetworkManager: NetworkManagerProtocol {
// Call tracking
var fetchUserCallCount = 0
var lastFetchedUserId: String?
var saveUserCallCount = 0
var lastSavedUser: User?
// Configurable results
var fetchUserResult: Result<User, Error>?
var saveUserResult: Result<Void, Error> = .success(())
func fetchUser(id: String) async throws -> User {
fetchUserCallCount += 1
lastFetchedUserId = id
switch fetchUserResult {
case .success(let user):
return user
case .failure(let error):
throw error
case .none:
throw TestError.noMockResult
}
}
func saveUser(_ user: User) async throws {
saveUserCallCount += 1
lastSavedUser = user
switch saveUserResult {
case .success:
return
case .failure(let error):
throw error
}
}
// Reset for reuse
func reset() {
fetchUserCallCount = 0
lastFetchedUserId = nil
saveUserCallCount = 0
lastSavedUser = nil
fetchUserResult = nil
saveUserResult = .success(())
}
}
enum TestError: Error {
case noMockResult
}
class NetworkManagerSpy: NetworkManagerProtocol {
private(set) var messages: [Message] = []
enum Message: Equatable {
case fetchUser(id: String)
case saveUser(User)
}
var stubbedFetchUserResult: Result<User, Error> = .failure(TestError.noMockResult)
func fetchUser(id: String) async throws -> User {
messages.append(.fetchUser(id: id))
return try stubbedFetchUserResult.get()
}
func saveUser(_ user: User) async throws {
messages.append(.saveUser(user))
}
}
func test_fetchUser_withValidId_returnsUser() async throws {
// Arrange
let expectedUser = User(id: "123", name: "John Doe")
mockNetworkManager.fetchUserResult = .success(expectedUser)
// Act
let result = try await sut.fetchUser(id: "123")
// Assert
XCTAssertEqual(result, expectedUser)
}
func test_fetchUser_withNetworkError_throwsError() async {
// Arrange
mockNetworkManager.fetchUserResult = .failure(NetworkError.connectionFailed)
// Act & Assert
await XCTAssertThrowsError(try await sut.fetchUser(id: "123")) { error in
XCTAssertEqual(error as? NetworkError, .connectionFailed)
}
}
func test_notificationObserver_receivesNotification() {
// Arrange
let expectation = XCTestExpectation(description: "Notification received")
let notificationName = Notification.Name("TestNotification")
let observer = NotificationCenter.default.addObserver(
forName: notificationName,
object: nil,
queue: nil
) { _ in
expectation.fulfill()
}
// Act
NotificationCenter.default.post(name: notificationName, object: nil)
// Assert
wait(for: [expectation], timeout: 1.0)
// Cleanup
NotificationCenter.default.removeObserver(observer)
}
func test_delegateCallback_isCalledOnSuccess() {
// Arrange
let expectation = XCTestExpectation(description: "Delegate called")
let mockDelegate = MockDelegate()
mockDelegate.onSuccessCalled = { expectation.fulfill() }
sut.delegate = mockDelegate
// Act
sut.performOperation()
// Assert
wait(for: [expectation], timeout: 2.0)
XCTAssertTrue(mockDelegate.successCallCount == 1)
}
import Combine
func test_userPublisher_emitsUser() {
// Arrange
var receivedUser: User?
var receivedError: Error?
let expectation = XCTestExpectation(description: "Publisher emits")
let cancellable = sut.userPublisher
.sink(
receiveCompletion: { completion in
if case .failure(let error) = completion {
receivedError = error
}
expectation.fulfill()
},
receiveValue: { user in
receivedUser = user
}
)
// Act
sut.loadUser(id: "123")
// Assert
wait(for: [expectation], timeout: 2.0)
XCTAssertNotNil(receivedUser)
XCTAssertNil(receivedError)
cancellable.cancel()
}
class LoginViewControllerTests: XCTestCase {
var sut: LoginViewController!
var mockAuthService: MockAuthService!
override func setUpWithError() throws {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
sut = storyboard.instantiateViewController(
withIdentifier: "LoginViewController"
) as? LoginViewController
mockAuthService = MockAuthService()
sut.authService = mockAuthService
// Load view hierarchy
sut.loadViewIfNeeded()
}
override func tearDownWithError() throws {
sut = nil
mockAuthService = nil
}
func test_outlets_areConnected() {
XCTAssertNotNil(sut.emailTextField)
XCTAssertNotNil(sut.passwordTextField)
XCTAssertNotNil(sut.loginButton)
XCTAssertNotNil(sut.errorLabel)
}
func test_loginButton_tap_callsAuthService() {
// Arrange
sut.emailTextField.text = "[email protected]"
sut.passwordTextField.text = "password123"
// Act
sut.loginButton.sendActions(for: .touchUpInside)
// Assert
XCTAssertEqual(mockAuthService.loginCallCount, 1)
XCTAssertEqual(mockAuthService.lastLoginEmail, "[email protected]")
XCTAssertEqual(mockAuthService.lastLoginPassword, "password123")
}
func test_loginButton_withEmptyEmail_showsError() {
// Arrange
sut.emailTextField.text = ""
sut.passwordTextField.text = "password"
// Act
sut.loginButton.sendActions(for: .touchUpInside)
// Assert
XCTAssertEqual(mockAuthService.loginCallCount, 0)
XCTAssertFalse(sut.errorLabel.isHidden)
XCTAssertEqual(sut.errorLabel.text, "Email is required")
}
func test_successfulLogin_navigatesToHome() {
// Arrange
mockAuthService.loginResult = .success(User(id: "1", name: "Test"))
let mockNavigator = MockNavigator()
sut.navigator = mockNavigator
sut.emailTextField.text = "[email protected]"
sut.passwordTextField.text = "password"
// Act
sut.loginButton.sendActions(for: .touchUpInside)
// Assert
XCTAssertTrue(mockNavigator.didNavigateToHome)
}
}
func test_dataProcessing_performance() {
let largeDataSet = generateLargeDataSet(count: 10000)
measure {
_ = sut.processData(largeDataSet)
}
}
func test_dataProcessing_performanceWithOptions() {
let options = XCTMeasureOptions()
options.iterationCount = 10
measure(options: options) {
_ = sut.processData(generateLargeDataSet(count: 5000))
}
}
func test_memoryUsage_withLargeDataSet() {
let options = XCTMeasureOptions()
options.iterationCount = 5
measure(metrics: [XCTMemoryMetric()], options: options) {
autoreleasepool {
let data = sut.loadLargeDataSet()
sut.processData(data)
}
}
}
func test_cpuUsage_duringOperation() {
measure(metrics: [XCTCPUMetric()]) {
sut.performCPUIntensiveOperation()
}
}
func test_emailValidation_withVariousInputs() {
let testCases: [(email: String, isValid: Bool)] = [
("[email protected]", true),
("[email protected]", true),
("invalid.email", false),
("", false),
("@example.com", false),
("test@", false),
("[email protected]", false),
("test@domain", false)
]
for testCase in testCases {
let result = sut.isValidEmail(testCase.email)
XCTAssertEqual(
result,
testCase.isValid,
"Failed for email: '\(testCase.email)' - expected \(testCase.isValid), got \(result)"
)
}
}
// Using XCTestCase subclass for cleaner parameterized tests
class EmailValidationTests: XCTestCase {
struct TestCase {
let input: String
let expected: Bool
let file: StaticString
let line: UInt
init(_ input: String, _ expected: Bool,
file: StaticString = #file, line: UInt = #line) {
self.input = input
self.expected = expected
self.file = file
self.line = line
}
}
func test_isValidEmail() {
let testCases = [
TestCase("[email protected]", true),
TestCase("invalid", false),
TestCase("", false)
]
for testCase in testCases {
let result = EmailValidator.isValid(testCase.input)
XCTAssertEqual(result, testCase.expected,
file: testCase.file, line: testCase.line)
}
}
}
class LoginUITests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = ["--uitesting"]
app.launch()
}
func test_loginFlow_withValidCredentials_showsHomeScreen() {
// Navigate to login
let loginButton = app.buttons["LoginButton"]
XCTAssertTrue(loginButton.waitForExistence(timeout: 5))
// Enter credentials
let emailField = app.textFields["EmailTextField"]
emailField.tap()
emailField.typeText("[email protected]")
let passwordField = app.secureTextFields["PasswordTextField"]
passwordField.tap()
passwordField.typeText("password123")
// Tap login
loginButton.tap()
// Verify home screen
let homeTitle = app.staticTexts["Welcome"]
XCTAssertTrue(homeTitle.waitForExistence(timeout: 10))
}
func test_loginFlow_withInvalidCredentials_showsError() {
let emailField = app.textFields["EmailTextField"]
emailField.tap()
emailField.typeText("[email protected]")
let passwordField = app.secureTextFields["PasswordTextField"]
passwordField.tap()
passwordField.typeText("wrongpassword")
app.buttons["LoginButton"].tap()
let errorLabel = app.staticTexts["ErrorLabel"]
XCTAssertTrue(errorLabel.waitForExistence(timeout: 5))
XCTAssertEqual(errorLabel.label, "Invalid credentials")
}
}
test_scheme_configuration:
unit_tests:
targets: ["YourAppTests"]
coverage: true
parallel: true
ui_tests:
targets: ["YourAppUITests"]
coverage: false
parallel: false
launch_arguments: ["--uitesting", "--reset-state"]
integration_tests:
targets: ["YourAppIntegrationTests"]
coverage: true
parallel: false
{
"configurations" : [
{
"name" : "Unit Tests",
"options" : {
"targetForVariableExpansion" : { "target" : { "name" : "YourApp" } }
}
}
],
"defaultOptions" : {
"codeCoverage" : true,
"testTimeoutsEnabled" : true,
"defaultTestExecutionTimeAllowance" : 60
},
"testTargets" : [
{ "target" : { "name" : "YourAppTests" } }
],
"version" : 1
}
test_methodName_condition_expectedResultxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience