Testing in Swift: unit, integration, e2e from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: why testing is the foundation of a reliable Swift application

\n\n

Development for iOS and macOS requires high code reliability. An error in application logic can cost user experience, and in commercial projects — money. Testing in Swift is not just "good practice," but a mandatory development stage. It allows you to detect bugs at early stages, simplify refactoring, and improve code quality.

\n\n

In this article, we will break down three levels of testing: unit, integration, and e2e. You will learn how they differ, when to apply each one, and see working examples in Swift using the XCTest framework.

\n\n

1. Unit testing in Swift: the foundation

\n\n

Unit tests check individual modules (functions, methods, classes) in isolation. Their main goal is to ensure that each "unit" of code works correctly.

\n\n

Setting up XCTest

\n\n

XCTest is built into Xcode. To create a test target:

\n
    \n
  • Open your project in Xcode.
  • \n
  • Select File → New → Target.
  • \n
  • Select Unit Testing Bundle.
  • \n
\n\n

After creation, you will get a file like YourAppTests.swift.

\n\n

Unit test example

\n\n

Suppose we have a discount calculation function:

\n\n
func calculateDiscount(price: Double, discount: Double) -> Double {\n    guard discount >= 0 && discount <= 100 else { return price }\n    return price * (1 - discount / 100)\n}\n
\n\n

We write a test:

\n\n
import XCTest\n@testable import YourApp\n\nclass DiscountTests: XCTestCase {\n    func testCalculateDiscount_ValidInput() {\n        let result = calculateDiscount(price: 1000, discount: 20)\n        XCTAssertEqual(result, 800, accuracy: 0.01)\n    }\n    \n    func testCalculateDiscount_InvalidDiscount() {\n        let result = calculateDiscount(price: 500, discount: 150)\n        XCTAssertEqual(result, 500)\n    }\n}\n
\n\n

Best practices for unit tests:

\n
    \n
  • One test — one assertion.
  • \n
  • Use setUp() to initialize common objects.
  • \n
  • Avoid dependencies on network, database, or UI — use mocks.
  • \n
\n\n

2. Integration tests: checking component interaction

\n\n

Integration tests check how different modules work together. For example, the interaction of a data model with a network layer or a CoreData database.

\n\n

Example: testing a network request

\n\n

Let's create a simple service:

\n\n
class NetworkService {\n    func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {\n        // Real request to API\n    }\n}\n
\n\n

An integration test can verify that the service correctly handles a response from a real server (or from a local mock server):

\n\n
class NetworkServiceIntegrationTests: XCTestCase {\n    var service: NetworkService!\n    \n    override func setUp() {\n        super.setUp()\n        service = NetworkService()\n    }\n    \n    func testFetchUser_ReturnsUser() {\n        let expectation = self.expectation(description: "Fetch user")\n        \n        service.fetchUser { result in\n            switch result {\n            case .success(let user):\n                XCTAssertNotNil(user.name)\n            case .failure:\n                XCTFail("Request failed")\n            }\n            expectation.fulfill()\n        }\n        \n        waitForExpectations(timeout: 5)\n    }\n}\n
\n\n

Important: Integration tests are slower than unit tests, so they are run less frequently (e.g., before merging to main).

\n\n

3. E2E testing (end-to-end) with XCUITest

\n\n

E2E tests (End-to-End) simulate user actions: button presses, text input, UI display verification. In Swift, this is done using

Blogs

Book Recommendations