3 votes

Test unitaire contrôleur de vue présenté et clavier en iOS

Je cherche à écrire des tests unitaires pour m'assurer que le clavier et les contrôleurs de vue présentés sont déclenchés correctement, mais je rencontre un comportement étrange que je ne comprends pas et que je pense être lié à la façon dont UIWindow fonctionne. J'utilise Quick et Nimble, mais j'ai testé avec le XCTest de base et j'ai les mêmes problèmes.

Mon code :

import Quick
import Nimble

class TestSpec: QuickSpec {
    override func spec() {

        let sut = UIViewController()

        // La fenêtre est nulle lorsque ce test est exécuté en isolation
        UIApplication.shared.keyWindow?.rootViewController = sut

        // Cela ne fonctionne pas non plus
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = sut
        window.makeKeyAndVisible()

        describe("ViewController") {

            it("présente un UIAlertController") {
                let alert = UIAlertController(title: "Test", message: "Ceci est un test", preferredStyle: .alert)
                let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
                alert.addAction(okAction)

                sut.present(alert, animated: true, completion: nil)
                expect(sut.presentedViewController).toEventually(beAnInstanceOf(UIAlertController.self))
            }
        }
    }
}

Au début, je ne plaçais pas le contrôleur de vue dans une fenêtre, ce qui l'empêchait de présenter d'autres contrôleurs de vue. Maintenant, j'essaie de le placer dans une fenêtre, mais cela ne fonctionne pas non plus. Lorsque ce test est exécuté en isolation, la fenêtre est toujours nulle. Lorsque je le lance avec plusieurs autres tests, la fenêtre n'est parfois pas nulle, mais les tests échouent toujours. Il y a eu une brève période où les tests réussissaient, mais je ne parviens plus à reproduire cela pour une raison quelconque.

Des idées sur ce qui se passe ?

10voto

mokagio Points 1427

Merci d'avoir ouvert cette question, je n'aurais pas pensé au problème de UIWindow si je n'étais pas tombé dessus.

Après avoir bidouillé un peu avec votre code, j'ai réussi à faire passer mon test en faisant ceci :

beforeEach {
  let window = UIWindow(frame: UIScreen.main.bounds)
  window.makeKeyAndVisible()
  window.rootViewController = sut
  _ = sut.view
}

Deux choses à noter :

let window = UIWindow(frame: UIScreen.main.bounds)

Dans mes tests, la valeur de UIApplication.shared.keyWindow est nil. Je ne sais pas si c'est aussi le cas pour vous, j'ai empêché les tests unitaires de charger le UIAppDelegate ce qui pourrait avoir un lien avec cela.

À noter également que l'instance de la fenêtre n'est pas conservée par la classe de spécification. Je pense que cela est inutile car quand elle est déclarée fenêtre principale, l'instance de UIApplication la retient et UIApplication ne sera pas libérée.

_ = sut.view

Cette ligne est ce qui fait l'astuce. Tenter d'accéder à la vue du contrôleur de vue entraîne son ajout effectif à la hiérarchie des vues (plus d'informations ici), ce qui résout l'avertissement que l'on obtiendrait sinon :

Avertissement : Tentative de présenter 
sur  dont la vue n'est pas dans la hiérarchie 
de la fenêtre !

J'espère que cela vous aidera ;)

0voto

DisableR Points 389

Vous pouvez faire en sorte que le délégué de votre application soit un singleton. Dans ce cas, vous pourrez accéder à la fenêtre de l'application. Par exemple :

[AppDelegate sharedDelegate].window

Une autre approche consiste à créer dans vos tests une nouvelle fenêtre, la rendre keyAndVisible, lui assigner un rootViewController et la manipuler de manière indépendante.

0voto

Stew Points 1301

J'ai déjà essayé l'approche de mon mokagio avec succès par le passé, donc je suis heureux de voir que cela est également présenté ici. Il semble parfois poser des problèmes avec l'exécution réelle de la suite de tests, donc je l'ai évité récemment. Peut-être est-ce un changement dans le fonctionnement du testeur iOS.

J'ai fini par créer un extrait de code qui simule simplement la présentation, donc je peux tester l'interaction sans m'inquiéter du besoin effectif d'afficher des écrans, par exemple.

class MockPresentingViewController: UIViewController {
  var presentViewControllerTarget: UIViewController?

  override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
    presentViewControllerTarget = viewControllerToPresent
  }
}

Et ensuite dans mes tests, je peux :

// action
container.present(viewController, animated: true, completion: nil)

// expectation
expect(host.presentViewControllerTarget).to(...)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X