Guide des tests mobiles : Unités, intégration et UI

RÉSUMÉ

[Développement Mobile] Tester vos applications mobiles en 2026 : Guide complet des tests unitaires, d’intégration et UI

Maîtrisez les tests unitaires, d’intégration et UI pour Android et iOS afin de garantir la qualité de vos applications mobiles en 2026.

Keywords: Développement mobile, Tests mobiles, Qualité logicielle

TABLE DES MATIÈRES

1 Introduction : L’Impératif du Testing Mobile en 2026

2 Les Fondamentaux du Test Unitaire

3 Les Tests d’Intégration : Vérifier les Interactions

4 Les Tests UI : L’Expérience Utilisateur à la Loupe

5 Défis et Solutions Courantes en Testing Mobile

6 Stratégies de Test Avancées et Automatisation

7 FAQ sur le Testing Mobile

8 Conclusion : Vers des Applications Mobiles Infaillibles

INTRODUCTION

L’Impératif du Testing Mobile en 2026

Dans le paysage numérique de 2026, les applications mobiles sont plus qu’une simple commodité ; elles sont devenues le pilier de notre interaction avec le monde. Des services bancaires aux réseaux sociaux, en passant par le divertissement et la productivité, nos vies sont intrinsèquement liées à la performance et à la fiabilité de ces outils. Cependant, la complexité croissante des écosystèmes mobiles, caractérisée par une multitude de dispositifs, de systèmes d’exploitation (Android et iOS) et de versions, pose un défi majeur : garantir une qualité irréprochable.

L’échec d’une application peut avoir des répercussions désastreuses. Des études récentes montrent qu’en 2026, plus de 70% des utilisateurs désinstalleraient une application après seulement une ou deux expériences négatives, qu’il s’agisse de plantages, de lenteurs ou d’une interface utilisateur défectueuse. Pour les entreprises, cela se traduit par une perte de revenus, une dégradation de la réputation de la marque et un gaspillage des efforts de développement. Le testing mobile n’est donc plus une option, mais une nécessité stratégique pour toute organisation souhaitant prospérer dans l’économie des applications.

« Une application non testée est une application en attente d’un échec. »

— Kwontenu, Expert en Qualité Logicielle

Ce guide complet vous plongera dans les arcanes des tests unitaires, d’intégration et UI, essentiels pour le développement d’applications Android et iOS en 2026. Nous démystifierons le jargon technique et vous fournirons les meilleures pratiques, les outils incontournables et des exemples concrets pour chaque type de test. Notre objectif est de vous outiller pour construire des applications robustes, performantes et offrant une expérience utilisateur exceptionnelle, en minimisant les risques de défauts et en maximisant la satisfaction de vos utilisateurs.

POINT CLÉ

En 2026, la fragmentation du marché mobile (plus de 25 000 modèles Android, multiples versions iOS) rend le testing manuel insuffisant. L’automatisation est cruciale pour une couverture exhaustive et une livraison rapide.


CONTENU PRINCIPAL

Les Fondamentaux du Test Unitaire

Les tests unitaires sont la première ligne de défense de votre code. Ils visent à tester les plus petites parties testables d’une application, appelées « unités », de manière isolée. Une unité peut être une fonction, une méthode, une classe ou un composant. L’objectif est de vérifier que chaque unité fonctionne comme prévu, indépendamment des autres parties du système. Cela permet de détecter les bugs très tôt dans le cycle de développement, là où ils sont les moins coûteux à corriger.

Les avantages des tests unitaires sont multiples :

  • Détection précoce des bugs : Les problèmes sont identifiés avant qu’ils ne s’intègrent dans des systèmes plus complexes.
  • Confiance dans le refactoring : Modifier le code devient moins risqué, car les tests garantissent que les fonctionnalités existantes ne sont pas cassées.
  • Amélioration de la conception du code : Écrire des tests unitaires encourage un code plus modulaire, découplé et facile à maintenir.
  • Documentation vivante : Les tests servent de documentation sur le comportement attendu de chaque unité.
  • Feedback rapide : Les tests unitaires s’exécutent généralement en quelques millisecondes, offrant un retour immédiat au développeur.

POINT CLÉ

Une bonne suite de tests unitaires peut réduire le temps de débogage de plus de 50% et augmenter la productivité des développeurs de 15% à 30%.

Sur Android, JUnit est le framework de test unitaire standard, souvent combiné avec Mockito ou MockK pour la création de mocks et de stubs. Pour iOS, XCTest est le framework natif fourni par Apple.

Exemple de Test Unitaire Android (Kotlin)

EXPLICATION DU CODE

Cet exemple simple teste une fonction Calculator.add(). Nous utilisons JUnit et la fonction assertEquals pour vérifier que le résultat de l’addition est correct.

// src/main/java/com/kwontenu/app/Calculator.kt
package com.kwontenu.app

class Calculator {
    fun add(a: Int, b: Int): Int {
        return a + b
    }

    fun subtract(a: Int, b: Int): Int {
        return a - b
    }
}

// src/test/java/com/kwontenu/app/CalculatorTest.kt
package com.kwontenu.app

import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

class CalculatorTest {

    private lateinit var calculator: Calculator

    @Before
    fun setup() {
        calculator = Calculator()
    }

    @Test
    fun add_returnsCorrectSum() {
        val result = calculator.add(5, 3)
        assertEquals(8, result)
    }

    @Test
    fun subtract_returnsCorrectDifference() {
        val result = calculator.subtract(10, 4)
        assertEquals(6, result)
    }

    @Test
    fun add_returnsCorrectSum_negativeNumbers() {
        val result = calculator.add(-5, -3)
        assertEquals(-8, result)
    }
}

Exemple de Test Unitaire iOS (Swift)

EXPLICATION DU CODE

Cet exemple utilise XCTest pour tester une structure MathOperations. La méthode XCTAssertEqual est utilisée pour affirmer que les résultats des opérations sont conformes aux attentes.

// MathOperations.swift
import Foundation

struct MathOperations {
    func add(a: Int, b: Int) -> Int {
        return a + b
    }

    func multiply(a: Int, b: Int) -> Int {
        return a * b
    }
}

// MathOperationsTests.swift (dans le target de test)
import XCTest
@testable import YourAppModule // Remplacez par le nom de votre module

class MathOperationsTests: XCTestCase {

    var mathOps: MathOperations!

    override func setUpWithError() throws {
        // Cette méthode est appelée avant l'invocation de chaque méthode de test.
        mathOps = MathOperations()
    }

    override func tearDownWithError() throws {
        // Cette méthode est appelée après l'invocation de chaque méthode de test.
        mathOps = nil
    }

    func testAddFunction_ReturnsCorrectSum() {
        let result = mathOps.add(a: 10, b: 5)
        XCTAssertEqual(result, 15, "La fonction add() devrait retourner 15.")
    }

    func testMultiplyFunction_ReturnsCorrectProduct() {
        let result = mathOps.multiply(a: 4, b: 3)
        XCTAssertEqual(result, 12, "La fonction multiply() devrait retourner 12.")
    }

    func testAddFunction_WithNegativeNumbers() {
        let result = mathOps.add(a: -7, b: 2)
        XCTAssertEqual(result, -5, "La fonction add() devrait gérer les nombres négatifs.")
    }
}

Diagramme illustrant le concept des tests unitaires, montrant des composants isolés testés

Pour maximiser l’efficacité de vos tests unitaires, suivez les principes F.I.R.S.T. :

  • Fast (Rapides) : Les tests doivent s’exécuter rapidement pour un feedback immédiat.
  • Independent (Indépendants) : Chaque test doit pouvoir s’exécuter seul, dans n’importe quel ordre.
  • Repeatable (Répétables) : Les résultats doivent être les mêmes à chaque exécution, quel que soit l’environnement.
  • Self-validating (Auto-validants) : Le test doit clairement indiquer s’il a réussi ou échoué.
  • Timely (Opportuns) : Les tests doivent être écrits juste avant ou pendant le développement du code à tester (approche TDD).

CONTENU PRINCIPAL

Les Tests d’Intégration : Vérifier les Interactions

Après avoir vérifié les unités individuelles, il est crucial de s’assurer qu’elles fonctionnent correctement ensemble. C’est le rôle des tests d’intégration. Ces tests visent à vérifier l’interaction entre différents modules ou services d’une application, tels que la communication entre un ViewModel et un Repository, l’accès à une base de données locale, ou les appels à une API externe. L’objectif est de s’assurer que les interfaces entre les composants sont correctement implémentées et que le flux de données entre eux est cohérent.

Les tests d’intégration sont particulièrement importants pour :

  • Valider les contrats d’interface : S’assurer que les composants se comprennent et échangent les données comme prévu.
  • Tester les flux complexes : Vérifier des scénarios impliquant plusieurs étapes et interactions entre des couches différentes.
  • Détecter les problèmes de dépendance : Identifier les erreurs liées aux configurations ou aux versions des bibliothèques.
  • Assurer la persistance des données : Tester la lecture et l’écriture dans des bases de données locales (Room sur Android, Core Data/Realm sur iOS).
  • Vérifier les interactions réseau : Simuler des appels API et s’assurer que l’application gère correctement les réponses et les erreurs.

« Les tests d’intégration sont le pont entre les unités isolées et le système cohérent. »

— Kwontenu, Architecte Logiciel

Les outils utilisés pour les tests d’intégration sont souvent une extension des frameworks de tests unitaires. Pour Android, AndroidX Test fournit des bibliothèques pour tester des composants Android dans un environnement Android réel ou simulé. Des frameworks de Dependency Injection comme Hilt ou Koin facilitent l’injection de mocks pour isoler les composants autant que possible. Sur iOS, XCTest est également utilisé, souvent avec des mocks pour les dépendances externes comme les services réseau.

POINT CLÉ

Les tests d’intégration sont plus lents que les tests unitaires mais plus rapides que les tests UI. Un bon équilibre est crucial pour une stratégie de test efficace.

Exemple de Test d’Intégration Android (Kotlin – ViewModel/Repository)

EXPLICATION DU CODE

Cet exemple teste l’intégration entre un UserViewModel et un UserRepository. Nous utilisons Mockito pour mocker le repository et simuler son comportement, ce qui nous permet de tester le ViewModel de manière isolée des dépendances réelles (comme une base de données ou un service réseau).

// src/main/java/com/kwontenu/app/data/UserRepository.kt
package com.kwontenu.app.data

import com.kwontenu.app.model.User

interface UserRepository {
    suspend fun getUsers(): List
    suspend fun getUserById(id: String): User?
}

// src/main/java/com/kwontenu/app/viewmodel/UserViewModel.kt
package com.kwontenu.app.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kwontenu.app.data.UserRepository
import com.kwontenu.app.model.User
import kotlinx.coroutines.launch

class UserViewModel(private val userRepository: UserRepository) : ViewModel() {

    private val _users = MutableLiveData>()
    val users: LiveData> = _users

    private val _isLoading = MutableLiveData()
    val isLoading: LiveData = _isLoading

    fun fetchUsers() {
        _isLoading.value = true
        viewModelScope.launch {
            try {
                _users.value = userRepository.getUsers()
            } catch (e: Exception) {
                // Gérer l'erreur
            } finally {
                _isLoading.value = false
            }
        }
    }
}

// src/test/java/com/kwontenu/app/viewmodel/UserViewModelTest.kt
package com.kwontenu.app.viewmodel

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.kwontenu.app.data.UserRepository
import com.kwontenu.app.model.User
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.*
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.*

@ExperimentalCoroutinesApi
class UserViewModelTest {

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule() // Pour tester LiveData

    private val testDispatcher = UnconfinedTestDispatcher()
    private lateinit var userRepository: UserRepository
    private lateinit var viewModel: UserViewModel

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
        userRepository = mock(UserRepository::class.java)
        viewModel = UserViewModel(userRepository)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }

    @Test
    fun fetchUsers_loadsUsersIntoLiveData() = runTest {
        val dummyUsers = listOf(User("1", "Alice"), User("2", "Bob"))
        `when`(userRepository.getUsers()).thenReturn(dummyUsers)

        viewModel.fetchUsers()

        verify(userRepository).getUsers()
        assert(viewModel.users.value == dummyUsers)
        assert(viewModel.isLoading.value == false)
    }

    @Test
    fun fetchUsers_setsLoadingState() = runTest {
        `when`(userRepository.getUsers()).thenReturn(emptyList())

        viewModel.fetchUsers()

        assert(viewModel.isLoading.value == false) // Après la fin de l'appel
    }
}

Diagramme montrant les tests d'intégration entre un ViewModel et un Repository avec des dépendances mockées

Exemple de Test d’Intégration iOS (Swift – Service/NetworkClient)

EXPLICATION DU CODE

Ici, nous testons l’intégration entre un UserService et un NetworkClient. Le MockNetworkClient simule les réponses réseau, nous permettant de vérifier que le service gère correctement les données reçues sans effectuer de véritables appels réseau.

// User.swift
import Foundation

struct User: Codable, Equatable {
    let id: Int
    let name: String
    let email: String
}

// NetworkClient.swift
import Foundation

protocol NetworkClient {
    func fetchData(from url: URL) async throws -> Data
}

class URLSessionNetworkClient: NetworkClient {
    func fetchData(from url: URL) async throws -> Data {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
}

// UserService.swift
import Foundation

class UserService {
    private let networkClient: NetworkClient
    private let usersURL = URL(string: "https://api.example.com/users")!

    init(networkClient: NetworkClient) {
        self.networkClient = networkClient
    }

    func fetchUsers() async throws -> [User] {
        let data = try await networkClient.fetchData(from: usersURL)
        let decoder = JSONDecoder()
        return try decoder.decode([User].self, from: data)
    }
}

// MockNetworkClient.swift (dans le target de test)
import Foundation
@testable import YourAppModule // Remplacez par le nom de votre module

class MockNetworkClient: NetworkClient {
    var dataToReturn: Data?
    var errorToThrow: Error?
    var receivedURL: URL?

    func fetchData(from url: URL) async throws -> Data {
        receivedURL = url
        if let error = errorToThrow {
            throw error
        }
        guard let data = dataToReturn else {
            XCTFail("dataToReturn n'est pas défini pour le MockNetworkClient.")
            throw URLError(.unknown)
        }
        return data
    }
}

// UserServiceIntegrationTests.swift (dans le target de test)
import XCTest
@testable import YourAppModule // Remplacez par le nom de votre module

class UserServiceIntegrationTests: XCTestCase {

    var mockNetworkClient: MockNetworkClient!
    var userService: UserService!

    override func setUpWithError() throws {
        mockNetworkClient = MockNetworkClient()
        userService = UserService(networkClient: mockNetworkClient)
    }

    override func tearDownWithError() throws {
        mockNetworkClient = nil
        userService = nil
    }

    func testFetchUsers_Success() async throws {
        let json = """
        [
            {"id": 1, "name": "Alice", "email": "[email protected]"},
            {"id": 2, "name": "Bob", "email": "[email protected]"}
        ]
        """
        mockNetworkClient.dataToReturn = json.data(using: .utf8)

        let users = try await userService.fetchUsers()

        XCTAssertEqual(users.count, 2)
        XCTAssertEqual(users[0].name, "Alice")
        XCTAssertEqual(mockNetworkClient.receivedURL?.absoluteString, "https://api.example.com/users")
    }

    func testFetchUsers_NetworkError() async {
        mockNetworkClient.errorToThrow = URLError(.notConnectedToInternet)

        do {
            _ = try await userService.fetchUsers()
            XCTFail("La récupération des utilisateurs devrait échouer avec une erreur réseau.")
        } catch {
            XCTAssertTrue(error is URLError)
            let urlError = error as! URLError
            XCTAssertEqual(urlError.code, .notConnectedToInternet)
        }
    }
}

Diagramme montrant les tests d'intégration entre un ViewModel et un Repository avec des dépendances mockées

Exemple de Test d’Intégration iOS (Swift – Service/NetworkClient)

EXPLICATION DU CODE

Ici, nous testons l’intégration entre un UserService et un NetworkClient. Le MockNetworkClient simule les réponses réseau, nous permettant de vérifier que le service gère correctement les données reçues sans effectuer de véritables appels réseau.

// User.swift
import Foundation

struct User: Codable, Equatable {
    let id: Int
    let name: String
    let email: String
}

// NetworkClient.swift
import Foundation

protocol NetworkClient {
    func fetchData(from url: URL) async throws -> Data
}

class URLSessionNetworkClient: NetworkClient {
    func fetchData(from url: URL) async throws -> Data {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
}

// UserService.swift
import Foundation

class UserService {
    private let networkClient: NetworkClient
    private let usersURL = URL(string: "https://api.example.com/users")!

    init(networkClient: NetworkClient) {
        self.networkClient = networkClient
    }

    func fetchUsers() async throws -> [User] {
        let data = try await networkClient.fetchData(from: usersURL)
        let decoder = JSONDecoder()
        return try decoder.decode([User].self, from: data)
    }
}

// MockNetworkClient.swift (dans le target de test)
import Foundation
@testable import YourAppModule // Remplacez par le nom de votre module

class MockNetworkClient: NetworkClient {
    var dataToReturn: Data?
    var errorToThrow: Error?
    var receivedURL: URL?

    func fetchData(from url: URL) async throws -> Data {
        receivedURL = url
        if let error = errorToThrow {
            throw error
        }
        guard let data = dataToReturn else {
            XCTFail("dataToReturn n'est pas défini pour le MockNetworkClient.")
            throw URLError(.unknown)
        }
        return data
    }
}

// UserServiceIntegrationTests.swift (dans le target de test)
import XCTest
@testable import YourAppModule // Remplacez par le nom de votre module

class UserServiceIntegrationTests: XCTestCase {

    var mockNetworkClient: MockNetworkClient!
    var userService: UserService!

    override func setUpWithError() throws {
        mockNetworkClient = MockNetworkClient()
        userService = UserService(networkClient: mockNetworkClient)
    }

    override func tearDownWithError() throws {
        mockNetworkClient = nil
        userService = nil
    }

    func testFetchUsers_Success() async throws {
        let json = """
        [
            {"id": 1, "name": "Alice", "email": "[email protected]"},
            {"id": 2, "name": "Bob", "email": "[email protected]"}
        ]
        """
        mockNetworkClient.dataToReturn = json.data(using: .utf8)

        let users = try await userService.fetchUsers()

        XCTAssertEqual(users.count, 2)
        XCTAssertEqual(users[0].name, "Alice")
        XCTAssertEqual(mockNetworkClient.receivedURL?.absoluteString, "https://api.example.com/users")
    }

    func testFetchUsers_NetworkError() async {
        mockNetworkClient.errorToThrow = URLError(.notConnectedToInternet)

        do {
            _ = try await userService.fetchUsers()
            XCTFail("La récupération des utilisateurs devrait échouer avec une erreur réseau.")
        } catch {
            XCTAssertTrue(error is URLError)
            let urlError = error as! URLError
            XCTAssertEqual(urlError.code, .notConnectedToInternet)
        }
    }
}

Diagramme montrant les tests d'intégration entre un ViewModel et un Repository avec des dépendances mockées

Exemple de Test d’Intégration iOS (Swift – Service/NetworkClient)

EXPLICATION DU CODE

Ici, nous testons l’intégration entre un UserService et un NetworkClient. Le MockNetworkClient simule les réponses réseau, nous permettant de vérifier que le service gère correctement les données reçues sans effectuer de véritables appels réseau.

// User.swift
import Foundation

struct User: Codable, Equatable {
    let id: Int
    let name: String
    let email: String
}

// NetworkClient.swift
import Foundation

protocol NetworkClient {
    func fetchData(from url: URL) async throws -> Data
}

class URLSessionNetworkClient: NetworkClient {
    func fetchData(from url: URL) async throws -> Data {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
}

// UserService.swift
import Foundation

class UserService {
    private let networkClient: NetworkClient
    private let usersURL = URL(string: "https://api.example.com/users")!

    init(networkClient: NetworkClient) {
        self.networkClient = networkClient
    }

    func fetchUsers() async throws -> [User] {
        let data = try await networkClient.fetchData(from: usersURL)
        let decoder = JSONDecoder()
        return try decoder.decode([User].self, from: data)
    }
}

// MockNetworkClient.swift (dans le target de test)
import Foundation
@testable import YourAppModule // Remplacez par le nom de votre module

class MockNetworkClient: NetworkClient {
    var dataToReturn: Data?
    var errorToThrow: Error?
    var receivedURL: URL?

    func fetchData(from url: URL) async throws -> Data {
        receivedURL = url
        if let error = errorToThrow {
            throw error
        }
        guard let data = dataToReturn else {
            XCTFail("dataToReturn n'est pas défini pour le MockNetworkClient.")
            throw URLError(.unknown)
        }
        return data
    }
}

// UserServiceIntegrationTests.swift (dans le target de test)
import XCTest
@testable import YourAppModule // Remplacez par le nom de votre module

class UserServiceIntegrationTests: XCTestCase {

    var mockNetworkClient: MockNetworkClient!
    var userService: UserService!

    override func setUpWithError() throws {
        mockNetworkClient = MockNetworkClient()
        userService = UserService(networkClient: mockNetworkClient)
    }

    override func tearDownWithError() throws {
        mockNetworkClient = nil
        userService = nil
    }

    func testFetchUsers_Success() async throws {
        let json = """
        [
            {"id": 1, "name": "Alice", "email": "[email protected]"},
            {"id": 2, "name": "Bob", "email": "[email protected]"}
        ]
        """
        mockNetworkClient.dataToReturn = json.data(using: .utf8)

        let users = try await userService.fetchUsers()

        XCTAssertEqual(users.count, 2)
        XCTAssertEqual(users[0].name, "Alice")
        XCTAssertEqual(mockNetworkClient.receivedURL?.absoluteString, "https://api.example.com/users")
    }

    func testFetchUsers_NetworkError() async {
        mockNetworkClient.errorToThrow = URLError(.notConnectedToInternet)

        do {
            _ = try await userService.fetchUsers()
            XCTFail("La récupération des utilisateurs devrait échouer avec une erreur réseau.")
        } catch {
            XCTAssertTrue(error is URLError)
            let urlError = error as! URLError
            XCTAssertEqual(urlError.code, .notConnectedToInternet)
        }
    }
}

Diagramme montrant les tests d'intégration entre un ViewModel et un Repository avec des dépendances mockées

Exemple de Test d’Intégration iOS (Swift – Service/NetworkClient)

EXPLICATION DU CODE

Ici, nous testons l’intégration entre un UserService et un NetworkClient. Le MockNetworkClient simule les réponses réseau, nous permettant de vérifier que le service gère correctement les données reçues sans effectuer de véritables appels réseau.

// User.swift
import Foundation

struct User: Codable, Equatable {
    let id: Int
    let name: String
    let email: String
}

// NetworkClient.swift
import Foundation

protocol NetworkClient {
    func fetchData(from url: URL) async throws -> Data
}

class URLSessionNetworkClient: NetworkClient {
    func fetchData(from url: URL) async throws -> Data {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
}

// UserService.swift
import Foundation

class UserService {
    private let networkClient: NetworkClient
    private let usersURL = URL(string: "https://api.example.com/users")!

    init(networkClient: NetworkClient) {
        self.networkClient = networkClient
    }

    func fetchUsers() async throws -> [User] {
        let data = try await networkClient.fetchData(from: usersURL)
        let decoder = JSONDecoder()
        return try decoder.decode([User].self, from: data)
    }
}

// MockNetworkClient.swift (dans le target de test)
import Foundation
@testable import YourAppModule // Remplacez par le nom de votre module

class MockNetworkClient: NetworkClient {
    var dataToReturn: Data?
    var errorToThrow: Error?
    var receivedURL: URL?

    func fetchData(from url: URL) async throws -> Data {
        receivedURL = url
        if let error = errorToThrow {
            throw error
        }
        guard let data = dataToReturn else {
            XCTFail("dataToReturn n'est pas défini pour le MockNetworkClient.")
            throw URLError(.unknown)
        }
        return data
    }
}

// UserServiceIntegrationTests.swift (dans le target de test)
import XCTest
@testable import YourAppModule // Remplacez par le nom de votre module

class UserServiceIntegrationTests: XCTestCase {

    var mockNetworkClient: MockNetworkClient!
    var userService: UserService!

    override func setUpWithError() throws {
        mockNetworkClient = MockNetworkClient()
        userService = UserService(networkClient: mockNetworkClient)
    }

    override func tearDownWithError() throws {
        mockNetworkClient = nil
        userService = nil
    }

    func testFetchUsers_Success() async throws {
        let json = """
        [
            {"id": 1, "name": "Alice", "email": "[email protected]"},
            {"id": 2, "name": "Bob", "email": "[email protected]"}
        ]
        """
        mockNetworkClient.dataToReturn = json.data(using: .utf8)

        let users = try await userService.fetchUsers()

        XCTAssertEqual(users.count, 2)
        XCTAssertEqual(users[0].name, "Alice")
        XCTAssertEqual(mockNetworkClient.receivedURL?.absoluteString, "https://api.example.com/users")
    }

    func testFetchUsers_NetworkError() async {
        mockNetworkClient.errorToThrow = URLError(.notConnectedToInternet)

        do {
            _ = try await userService.fetchUsers()
            XCTFail("La récupération des utilisateurs devrait échouer avec une erreur réseau.")
        } catch {
            XCTAssertTrue(error is URLError)
            let urlError = error as! URLError
            XCTAssertEqual(urlError.code, .notConnectedToInternet)
        }
    }
}

Diagramme montrant les tests d'intégration entre un ViewModel et un Repository avec des dépendances mockées

Exemple de Test d’Intégration iOS (Swift – Service/NetworkClient)

EXPLICATION DU CODE

Ici, nous testons l’intégration entre un UserService et un NetworkClient. Le MockNetworkClient simule les réponses réseau, nous permettant de vérifier que le service gère correctement les données reçues sans effectuer de véritables appels réseau.

// User.swift
import Foundation

struct User: Codable, Equatable {
    let id: Int
    let name: String
    let email: String
}

// NetworkClient.swift
import Foundation

protocol NetworkClient {
    func fetchData(from url: URL) async throws -> Data
}

class URLSessionNetworkClient: NetworkClient {
    func fetchData(from url: URL) async throws -> Data {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
}

// UserService.swift
import Foundation

class UserService {
    private let networkClient: NetworkClient
    private let usersURL = URL(string: "https://api.example.com/users")!

    init(networkClient: NetworkClient) {
        self.networkClient = networkClient
    }

    func fetchUsers() async throws -> [User] {
        let data = try await networkClient.fetchData(from: usersURL)
        let decoder = JSONDecoder()
        return try decoder.decode([User].self, from: data)
    }
}

// MockNetworkClient.swift (dans le target de test)
import Foundation
@testable import YourAppModule // Remplacez par le nom de votre module

class MockNetworkClient: NetworkClient {
    var dataToReturn: Data?
    var errorToThrow: Error?
    var receivedURL: URL?

    func fetchData(from url: URL) async throws -> Data {
        receivedURL = url
        if let error = errorToThrow {
            throw error
        }
        guard let data = dataToReturn else {
            XCTFail("dataToReturn n'est pas défini pour le MockNetworkClient.")
            throw URLError(.unknown)
        }
        return data
    }
}

// UserServiceIntegrationTests.swift (dans le target de test)
import XCTest
@testable import YourAppModule // Remplacez par le nom de votre module

class UserServiceIntegrationTests: XCTestCase {

    var mockNetworkClient: MockNetworkClient!
    var userService: UserService!

    override func setUpWithError() throws {
        mockNetworkClient = MockNetworkClient()
        userService = UserService(networkClient: mockNetworkClient)
    }

    override func tearDownWithError() throws {
        mockNetworkClient = nil
        userService = nil
    }

    func testFetchUsers_Success() async throws {
        let json = """
        [
            {"id": 1, "name": "Alice", "email": "[email protected]"},
            {"id": 2, "name": "Bob", "email": "[email protected]"}
        ]
        """
        mockNetworkClient.dataToReturn = json.data(using: .utf8)

        let users = try await userService.fetchUsers()

        XCTAssertEqual(users.count, 2)
        XCTAssertEqual(users[0].name, "Alice")
        XCTAssertEqual(mockNetworkClient.receivedURL?.absoluteString, "https://api.example.com/users")
    }

    func testFetchUsers_NetworkError() async {
        mockNetworkClient.errorToThrow = URLError(.notConnectedToInternet)

        do {
            _ = try await userService.fetchUsers()
            XCTFail("La récupération des utilisateurs devrait échouer avec une erreur réseau.")
        } catch {
            XCTAssertTrue(error is URLError)
            let urlError = error as! URLError
            XCTAssertEqual(urlError.code, .notConnectedToInternet)
        }
    }
}

Diagramme montrant les tests d'intégration entre un ViewModel et un Repository avec des dépendances mockées

Exemple de Test d’Intégration iOS (Swift – Service/NetworkClient)

EXPLICATION DU CODE

Ici, nous testons l’intégration entre un UserService et un NetworkClient. Le MockNetworkClient simule les réponses réseau, nous permettant de vérifier que le service gère correctement les données reçues sans effectuer de véritables appels réseau.

// User.swift
import Foundation

struct User: Codable, Equatable {
    let id: Int
    let name: String
    let email: String
}

// NetworkClient.swift
import Foundation

protocol NetworkClient {
    func fetchData(from url: URL) async throws -> Data
}

class URLSessionNetworkClient: NetworkClient {
    func fetchData(from url: URL) async throws -> Data {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
}

// UserService.swift
import Foundation

class UserService {
    private let networkClient: NetworkClient
    private let usersURL = URL(string: "https://api.example.com/users")!

    init(networkClient: NetworkClient) {
        self.networkClient = networkClient
    }

    func fetchUsers() async throws -> [User] {
        let data = try await networkClient.fetchData(from: usersURL)
        let decoder = JSONDecoder()
        return try decoder.decode([User].self, from: data)
    }
}

// MockNetworkClient.swift (dans le target de test)
import Foundation
@testable import YourAppModule // Remplacez par le nom de votre module

class MockNetworkClient: NetworkClient {
    var dataToReturn: Data?
    var errorToThrow: Error?
    var receivedURL: URL?

    func fetchData(from url: URL) async throws -> Data {
        receivedURL = url
        if let error = errorToThrow {
            throw error
        }
        guard let data = dataToReturn else {
            XCTFail("dataToReturn n'est pas défini pour le MockNetworkClient.")
            throw URLError(.unknown)
        }
        return data
    }
}

// UserServiceIntegrationTests.swift (dans le target de test)
import XCTest
@testable import YourAppModule // Remplacez par le nom de votre module

class UserServiceIntegrationTests: XCTestCase {

    var mockNetworkClient: MockNetworkClient!
    var userService: UserService!

    override func setUpWithError() throws {
        mockNetworkClient = MockNetworkClient()
        userService = UserService(networkClient: mockNetworkClient)
    }

    override func tearDownWithError() throws {
        mockNetworkClient = nil
        userService = nil
    }

    func testFetchUsers_Success() async throws {
        let json = """
        [
            {"id": 1, "name": "Alice", "email": "[email protected]"},
            {"id": 2, "name": "Bob", "email": "[email protected]"}
        ]
        """
        mockNetworkClient.dataToReturn = json.data(using: .utf8)

        let users = try await userService.fetchUsers()

        XCTAssertEqual(users.count, 2)
        XCTAssertEqual(users[0].name, "Alice")
        XCTAssertEqual(mockNetworkClient.receivedURL?.absoluteString, "https://api.example.com/users")
    }

    func testFetchUsers_NetworkError() async {
        mockNetworkClient.errorToThrow = URLError(.notConnectedToInternet)

        do {
            _ = try await userService.fetchUsers()
            XCTFail("La récupération des utilisateurs devrait échouer avec une erreur réseau.")
        } catch {
            XCTAssertTrue(error is URLError)
            let urlError = error as! URLError
            XCTAssertEqual(urlError.code, .notConnectedToInternet)
        }
    }
}

Diagramme montrant les tests d'intégration entre un ViewModel et un Repository avec des dépendances mockées

Exemple de Test d’Intégration iOS (Swift – Service/NetworkClient)

EXPLICATION DU CODE

Ici, nous testons l’intégration entre un UserService et un NetworkClient. Le MockNetworkClient simule les réponses réseau, nous permettant de vérifier que le service gère correctement les données reçues sans effectuer de véritables appels réseau.

// User.swift
import Foundation

struct User: Codable, Equatable {
    let id: Int
    let name: String
    let email: String
}

// NetworkClient.swift
import Foundation

protocol NetworkClient {
    func fetchData(from url: URL) async throws -> Data
}

class URLSessionNetworkClient: NetworkClient {
    func fetchData(from url: URL) async throws -> Data {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
}

// UserService.swift
import Foundation

class UserService {
    private let networkClient: NetworkClient
    private let usersURL = URL(string: "https://api.example.com/users")!

    init(networkClient: NetworkClient) {
        self.networkClient = networkClient
    }

    func fetchUsers() async throws -> [User] {
        let data = try await networkClient.fetchData(from: usersURL)
        let decoder = JSONDecoder()
        return try decoder.decode([User].self, from: data)
    }
}

// MockNetworkClient.swift (dans le target de test)
import Foundation
@testable import YourAppModule // Remplacez par le nom de votre module

class MockNetworkClient: NetworkClient {
    var dataToReturn: Data?
    var errorToThrow: Error?
    var receivedURL: URL?

    func fetchData(from url: URL) async throws -> Data {
        receivedURL = url
        if let error = errorToThrow {
            throw error
        }
        guard let data = dataToReturn else {
            XCTFail("dataToReturn n'est pas défini pour le MockNetworkClient.")
            throw URLError(.unknown)
        }
        return data
    }
}

// UserServiceIntegrationTests.swift (dans le target de test)
import XCTest
@testable import YourAppModule // Remplacez par le nom de votre module

class UserServiceIntegrationTests: XCTestCase {

    var mockNetworkClient: MockNetworkClient!
    var userService: UserService!

    override func setUpWithError() throws {
        mockNetworkClient = MockNetworkClient()
        userService = UserService(networkClient: mockNetworkClient)
    }

    override func tearDownWithError() throws {
        mockNetworkClient = nil
        userService = nil
    }

    func testFetchUsers_Success() async throws {
        let json = """
        [
            {"id": 1, "name": "Alice", "email": "[email protected]"},
            {"id": 2, "name": "Bob", "email": "[email protected]"}
        ]
        """
        mockNetworkClient.dataToReturn = json.data(using: .utf8)

        let users = try await userService.fetchUsers()

        XCTAssertEqual(users.count, 2)
        XCTAssertEqual(users[0].name, "Alice")
        XCTAssertEqual(mockNetworkClient.receivedURL?.absoluteString, "https://api.example.com/users")
    }

    func testFetchUsers_NetworkError() async {
        mockNetworkClient.errorToThrow = URLError(.notConnectedToInternet)

        do {
            _ = try await userService.fetchUsers()
            XCTFail("La récupération des utilisateurs devrait échouer avec une erreur réseau.")
        } catch {
            XCTAssertTrue(error is URLError)
            let urlError = error as! URLError
            XCTAssertEqual(urlError.code, .notConnectedToInternet)
        }
    }
}

Diagramme montrant les tests d'intégration entre un ViewModel et un Repository avec des dépendances mockées

Exemple de Test d’Intégration iOS (Swift – Service/NetworkClient)

EXPLICATION DU CODE

Ici, nous testons l’intégration entre un UserService et un NetworkClient. Le MockNetworkClient simule les réponses réseau, nous permettant de vérifier que le service gère correctement les données reçues sans effectuer de véritables appels réseau.

// User.swift
import Foundation

struct User: Codable, Equatable {
    let id: Int
    let name: String
    let email: String
}

// NetworkClient.swift
import Foundation

protocol NetworkClient {
    func fetchData(from url: URL) async throws -> Data
}

class URLSessionNetworkClient: NetworkClient {
    func fetchData(from url: URL) async throws -> Data {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
}

// UserService.swift
import Foundation

class UserService {
    private let networkClient: NetworkClient
    private let usersURL = URL(string: "https://api.example.com/users")!

    init(networkClient: NetworkClient) {
        self.networkClient = networkClient
    }

    func fetchUsers() async throws -> [User] {
        let data = try await networkClient.fetchData(from: usersURL)
        let decoder = JSONDecoder()
        return try decoder.decode([User].self, from: data)
    }
}

// MockNetworkClient.swift (dans le target de test)
import Foundation
@testable import YourAppModule // Remplacez par le nom de votre module

class MockNetworkClient: NetworkClient {
    var dataToReturn: Data?
    var errorToThrow: Error?
    var receivedURL: URL?

    func fetchData(from url: URL) async throws -> Data {
        receivedURL = url
        if let error = errorToThrow {
            throw error
        }
        guard let data = dataToReturn else {
            XCTFail("dataToReturn n'est pas défini pour le MockNetworkClient.")
            throw URLError(.unknown)
        }
        return data
    }
}

// UserServiceIntegrationTests.swift (dans le target de test)
import XCTest
@testable import YourAppModule // Remplacez par le nom de votre module

class UserServiceIntegrationTests: XCTestCase {

    var mockNetworkClient: MockNetworkClient!
    var userService: UserService!

    override func setUpWithError() throws {
        mockNetworkClient = MockNetworkClient()
        userService = UserService(networkClient: mockNetworkClient)
    }

    override func tearDownWithError() throws {
        mockNetworkClient = nil
        userService = nil
    }

    func testFetchUsers_Success() async throws {
        let json = """
        [
            {"id": 1, "name": "Alice", "email": "[email protected]"},
            {"id": 2, "name": "Bob", "email": "[email protected]"}
        ]
        """
        mockNetworkClient.dataToReturn = json.data(using: .utf8)

        let users = try await userService.fetchUsers()

        XCTAssertEqual(users.count, 2)
        XCTAssertEqual(users[0].name, "Alice")
        XCTAssertEqual(mockNetworkClient.receivedURL?.absoluteString, "https://api.example.com/users")
    }

    func testFetchUsers_NetworkError() async {
        mockNetworkClient.errorToThrow = URLError(.notConnectedToInternet)

        do {
            _ = try await userService.fetchUsers()
            XCTFail("La récupération des utilisateurs devrait échouer avec une erreur réseau.")
        } catch {
            XCTAssertTrue(error is URLError)
            let urlError = error as! URLError
            XCTAssertEqual(urlError.code, .notConnectedToInternet)
        }
    }
}

Diagramme montrant les tests d'intégration entre un ViewModel et un Repository avec des dépendances mockées

Exemple de Test d’Intégration iOS (Swift – Service/NetworkClient)

EXPLICATION DU CODE

Ici, nous testons l’intégration entre un UserService et un NetworkClient. Le MockNetworkClient simule les réponses réseau, nous permettant de vérifier que le service gère correctement les données reçues sans effectuer de véritables appels réseau.

// User.swift
import Foundation

struct User: Codable, Equatable {
let id: Int
let name: String
let email: String
}

// NetworkClient.swift
import Foundation

protocol NetworkClient {
func fetchData(from url: URL) async throws -> Data
}

class URLSessionNetworkClient: NetworkClient {
func fetchData(from url: URL) async throws -> Data {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return data
}
}

// UserService.swift
import Foundation

class UserService {