• 12 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 5/24/22

Rédigez vos tests

Le moment que vous attendiez tous est arrivé ! Nous allons rédiger nos tests !

Nous allons faire ensemble 5 tests. Ils vont correspondre aux 4 cas d'erreur du premier appel, et au cas où tout se passe bien sur les deux appels.

Rédigez le premier test

Pour le premier test, nous allons choisir le cas où il y a une erreur. Dans ce cas, dans le bloc de retour on envoie un callback d'erreur. Donc dans notre premier test, nous allons vérifier que si on reçoit une erreur, on a bien l'envoi de ce callback avec les bonnes valeurs.

Commençons à rédiger notre test :

func testGetQuoteShouldPostFailedCallbackIfError() {
// Given

// When

// Then
}

On souhaite tester la classe  QuoteService  , donc nous allons en créer une instance en utilisant l'initialiseur que nous avons créé à cet effet. Nous allons y mettre des instances de  URLSessionFake  qui correspondent à la situation que l'on souhaite tester ici.

// Given
let quoteService = QuoteService(
       quoteSession: URLSessionFake(data: nil, response: nil, error: FakeResponseData.error),
   imageSession: URLSessionFake(data: nil, response: nil, error: nil))

Je remplis mon  URLSessionFake  avec les valeurs de retour que je souhaite tester. Ici, je cherche juste à tester le cas où il y a une erreur. Donc je remplis seulement le paramètre erreur avec l'erreur que nous avons créée dans  FakeResponseData  .

Par ailleurs, je ne mets aucun paramètre dans  imageSession  , car je sais que je ne ne teste pas ici le second appel puisque le premier va déjà échouer.

Ensuite, j'écris mon When :

// When
quoteService.getQuote { (success, quote) in
}

C'est la fonction que je souhaite tester.

Enfin, nous allons écrire le Then. Nous allons vérifier que  success  vaut bien  false  , et que  quote  vaut bien  nil  . Notre Then a donc lieu dans notre fermeture.

// When
quoteService.getQuote { (success, quote) in

   // Then
   XCTAssertFalse(success)
   XCTAssertNil(quote)
}

Et voilà ! Vous pouvez tester... Ça marche ?

Euh... oui !

FAUX ! Vous n'êtes pas des testeurs vigilants  ! Essayez d'écrire  XCTAssertTrue  au lieu de  XCTAssertFalse  . Vous verrez qu'au mieux vos tests réussissent toujours, et qu’au pire cela fait crasher vos tests. Dans les deux cas, cela veut dire que vos tests ne fonctionnent pas !

Hmmm... En effet... mais pourquoi ?

La raison se trouve dans notre fonction  getQuote  , et plus précisément à cette ligne :

DispatchQueue.main.async { (...) }

Ici, on change de queue. Donc cela a lieu entre le When et le Then de notre test. Ce changement de queue nous fait perdre la notion d'exécution instantanée, car les instructions n'ont plus lieu les unes à la suite des autres mais dans des queues séparées. Du coup, ce microdécalage nous empêche de récupérer notre callback correctement.

Pour résoudre ce problème, nous allons créer un microdélai, qui va permettre d'analyser la réception du callback seulement à partir du moment où le changement de queue a eu lieu. Et nous allons créer pour cela une expectation , comme ceci :

// When
let expectation = XCTestExpectation(description: "Wait for queue change.")
quoteService.getQuote { (success, quote) in

   // Then
   XCTAssertFalse(success)
   XCTAssertNil(quote)
   expectation.fulfill()
}

wait(for: [expectation], timeout: 0.01)

Cela a lieu en trois temps :

  1. On crée un objet  expectation  en lui donnant simplement une description.

  2. Une fois le callback reçu, on appelle la méthode  fulfill  pour signifier que nous n'avons plus besoin d'attendre que l'expectation ait eu lieu.

  3. On fait attendre notre code 1 centième de seconde maximum, pour permettre au changement de queue d'avoir lieu.

Vous pouvez lancer votre test et vérifier que tout va bien !

Voici le code complet de notre test :

func testGetQuoteShouldPostFailedCallbackIfError() {
   // Given
   let quoteService = QuoteService(
           quoteSession: URLSessionFake(data: nil, response: nil, error: FakeResponseData.error),
      imageSession: URLSessionFake(data: nil, response: nil, error: nil))

   // When
      let expectation = XCTestExpectation(description: "Wait for queue change.")
   quoteService.getQuote { (success, quote) in

      // Then
      XCTAssertFalse(success)
      XCTAssertNil(quote)
      expectation.fulfill()
   }

   wait(for: [expectation], timeout: 0.01)
}

Testez les autres cas d’erreur

Nous allons maintenant tester les autres cas d'erreur. Dans tous les cas d'erreur, on doit vérifier que le callback d'erreur est bien envoyé. C'est donc la même chose que le test précédent. Seule la situation initiale, le Given, va varier.

L’appel ne renvoie pas de données

Nous allons tester le cas où l'appel ne renvoie pas de données :

func testGetQuoteShouldPostFailedCallbackIfNoData() {
   // Given
   let quoteService = QuoteService(
      quoteSession: URLSessionFake(data: nil, response: nil, error: nil),
      imageSession: URLSessionFake(data: nil, response: nil, error: nil))

   // (...)
}

Ici, on ne met aucune donnée mais également pas d'erreur ; le callback est censé être renvoyé.

La suite du test ne change pas, donc je ne vous la redonne pas.

Une réponse incorrecte

func testGetQuoteShouldPostFailedCallbackIfIncorrectResponse() {
   // Given
   let quoteService = QuoteService(
      quoteSession: URLSessionFake(
         data: FakeResponseData.quoteCorrectData,
         response: FakeResponseData.responseKO,
         error: nil),
      imageSession: URLSessionFake(data: nil, response: nil, error: nil))

   // (...)
}

Ici, je ne donne aucune erreur, mais de bonnes données, pour ne pas tomber dans les premiers cas précédents. Et je fournis un mauvais objet response. Il a notamment un code de 500 au lieu de 200.

Des données sont incorrectes

func testGetQuoteShouldPostFailedCallbackIfIncorrectData() {

   // Given
   let quoteService = QuoteService(
      quoteSession: URLSessionFake(
         data: FakeResponseData.quoteIncorrectData,
         response: FakeResponseData.responseOK,
         error: nil),
      imageSession: URLSessionFake(data: nil, response: nil, error: nil))

   // (...)
}

Ici, aucune erreur et une réponse correcte pour éviter les cas d'erreur précédents. Mais je fournis de mauvaises données pour tester le cas où le décodage du JSON échoue.

Quand tout va bien

Nous allons maintenant tester le cas où tout va bien. Dans ce cas, la méthode doit envoyer un callback qui contient un booléen avec la valeur  true  , et un objet  Quote  rempli avec les données reçues.

Démarrons par le Given :

func testGetQuoteShouldPostSuccessCallbackIfNoErrorAndCorrectData() {
   // Given
   let quoteService = QuoteService(
      quoteSession: URLSessionFake(
         data: FakeResponseData.quoteCorrectData,
         response: FakeResponseData.responseOK,
         error: nil),
   imageSession: URLSessionFake(
         data: FakeResponseData.imageData,
         response: FakeResponseData.responseOK,
         error: nil))
}

C'est le cas où tout va bien, donc nous remplissons le retour de la requête avec des données correctes, des réponses correctes et pas d'erreur.

Ensuite, le When ne change pas :

// When
let expectation = XCTestExpectation(description: "Wait for queue change.")
quoteService.getQuote { (success, quote) in
   // Then
}

On teste toujours la méthode  getQuote  .

Et ensuite, dans le Then, nous allons essayer de tester cette fois-ci le contenu de notre callback de succès :

// Then
XCTAssertTrue(success)
XCTAssertNotNil(quote)
expectation.fulfill()

Bien ! Vous pouvez tester et ça marche !

C'est du bon travail mais on doit aller plus loin, on doit tester que le contenu de l'objet  quote  correspond à ce que nous attendons.

Commençons par préparer les données que nous nous attendons à voir dans l'objet  Quote  renvoyé par le callback :

let text = "Face your deficiencies and acknowledge them; but do not let them master you. Let them teach you patience, sweetness, insight."
let author = "Helen Keller"
let imageData = "image".data(using: .utf8)!

Ici, je copie simplement les données du texte et de l'auteur contenues dans mon JSON de test, et je prépare des données  imageData  qui correspondent à celles que nous avions préparées dans  FakeResponseData  .

Et nous allons maintenant vérifier que les données correspondent, ce qui donne finalement :

quoteService.getQuote { (success, quote) in
   // Then
   XCTAssertTrue(success)
   XCTAssertNotNil(quote)

   let text = "Face your deficiencies and acknowledge them; but do not let them master you. Let them teach you patience, sweetness, insight."
   let author = "Helen Keller"
   let imageData = "image".data(using: .utf8)!

   XCTAssertEqual(text, quote!.text)
   XCTAssertEqual(author, quote!.author)
   XCTAssertEqual(imageData, quote!.imageData)

   expectation.fulfill()
}

Vous pouvez tester et ça marche !

À vous de jouer !

Vous aurez noté que nous n'avons pas du tout traité les cas d'erreur du deuxième appel. La raison ? Ils sont assez similaires à ceux du premier appel, et donc je vous laisse les faire pour vous entraîner !

Vous devez avoir 3 tests à ajouter.

Vous pourrez trouver la correction ici. Bonne chance !

En résumé

Pour rédiger des tests de qualité :

  • utilisez la mention  @testable  pour importer les classes de votre application dans votre fichier de test ;

  • utilisez un nom de test explicite ; 

  • utilisez le Behavior Driven Development pour structurer vos tests :

    • Given : c’est ici que vous initialisez vos variables,

    • When : c’est la fonction que vous souhaitez tester,

    • Then : c’est le résultat attendu ;

  • Expectation  vous permet de gérer l’asynchrone dans vos tests ;

  • pour tester la même fonction avec d’autres cas d’erreur, seul le Given varie.

Maintenant que vous avez écrit vos tests, rendez-vous au prochain chapitre pour vous entraîner avec l’application LogoViewer. Je ne vous en dis pas plus, allez vite voir !

Example of certificate of achievement
Example of certificate of achievement