web-dev-qa-db-fra.com

Comment se moquer d'une chaîne en utilisant mockito?

Je dois simuler un scénario de test dans lequel j'appelle la méthode getBytes() d'un objet String et j'obtiens une UnsupportedEncodingException.

J'ai essayé d'y parvenir en utilisant le code suivant:

String nonEncodedString = mock(String.class);
when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));

Le problème est que lorsque je lance mon scénario de test, une exception MockitoException indique que je ne peux pas me moquer d'une classe Java.lang.String.

Existe-t-il un moyen de simuler un objet String à l'aide de mockito ou, alternativement, de faire en sorte que mon objet String lève une exception UnsupportedEncodingException lorsque j'appelle la méthode getBytes?


Voici plus de détails pour illustrer le problème:

C'est la classe que je veux tester:

public final class A {
    public static String f(String str){
        try {
            return new String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // This is the catch block that I want to exercise.
            ...
        }
    }
}

Ceci est ma classe de test (j'utilise JUnit 4 et mockito):

public class TestA {

    @Test(expected=UnsupportedEncodingException.class)
    public void test(){
        String aString = mock(String.class);
        when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));
        A.f(aString);
    }
}
36
Alceu Costa

Le problème est que la classe String en Java est marquée comme finale, vous ne pouvez donc pas vous moquer des frameworks classiques Selon le Mockito FAQ , il s’agit également d’une limitation de ce cadre. 

39
Peter

Pourquoi ne pas créer une String avec un nom de codage incorrect? Voir

public String(byte bytes[], int offset, int length, String charsetName)

Se moquer de String est presque certainement une mauvaise idée.

13
Steve Freeman

Si tout ce que vous allez faire dans votre bloc catch consiste à lancer une exception d'exécution, vous pouvez vous enregistrer en tapant simplement en utilisant un objet Charset pour spécifier le nom de votre jeu de caractères.

public final class A{
    public static String f(String str){
        return new String(str.getBytes(Charset.forName("UTF-8")));
    }
}

De cette façon, vous n'attraperez pas une exception qui ne se produira jamais simplement parce que le compilateur vous le dit.

12
Martin Hilton

Comme d'autres l'ont indiqué, vous ne pouvez pas utiliser Mockito pour vous moquer d'un dernier cours. Cependant, le point le plus important est que le test n'est pas particulièrement utile car il montre simplement que String.getBytes() peut générer une exception, ce qu'il peut évidemment faire. Si vous souhaitez réellement tester cette fonctionnalité, vous pouvez ajouter un paramètre d’encodage à f() et envoyer une valeur incorrecte au test.

En outre, vous causez le même problème pour l'appelant de A.f() car A est final et f() est statique.

Cet article peut être utile pour convaincre vos collègues d’être moins dogmatiques sur la couverture de code à 100%: Comment échouer avec une couverture de test à 100% .

7
Jason

Dans sa documentation, JDave ne peut pas supprimer les modificateurs "finaux" des classes chargées par le chargeur de classes d'amorçage. Cela inclut toutes les classes JRE (de Java.lang, Java.util, etc.).

Un outil qui vous permet de vous moquer de tout est JMockit .

Avec JMockit, votre test peut être écrit comme suit:

import Java.io.*;
import org.junit.*;
import mockit.*;

public final class ATest
{
   @Test(expected = UnsupportedOperationException.class)
   public void test() throws Exception
   {
      new Expectations()
      {
         @Mocked("getBytes")
         String aString;

         {
            aString.getBytes(anyString);
            result = new UnsupportedEncodingException("Parsing error.");
         }
      };

      A.f("test");
   }
}

en supposant que la classe "A" complète est:

import Java.io.*;

public final class A
{
   public static String f(String str)
   {
      try {
         return new String(str.getBytes("UTF-8"));
      }
      catch (UnsupportedEncodingException e) {
         throw new UnsupportedOperationException(e);
      }
   }
}

J'ai effectivement exécuté ce test sur ma machine. (Remarquez que j'ai encapsulé l'exception d'origine vérifiée dans une exception d'exécution.)

J’ai moqué partiellement via @Mocked("getBytes") pour empêcher JMockit de se moquer de tout ce qui se trouve dans la classe Java.lang.String (imaginez ce que cela pourrait causer).

À présent, ce test est vraiment inutile, car "UTF-8" est un jeu de caractères standard, qui doit être pris en charge dans tous les JRE. Par conséquent, dans un environnement de production, le bloc d'interception ne sera jamais exécuté.

Le "besoin" ou le désir de couvrir le blocage reste valable, cependant. Alors, comment se débarrasser du test sans réduire le pourcentage de couverture? Voici mon idée: insérez une ligne avec assert false; comme première instruction à l'intérieur du bloc catch et demandez à l'outil Couverture de code d'ignorer tout le bloc catch lors de la déclaration des mesures de couverture. C'est l'un de mes "éléments TODO" pour la couverture JMockit. 8 ^)

5
Rogério

Vous allez tester du code qui ne peut jamais être exécuté. La prise en charge UTF-8 doit obligatoirement figurer sur chaque machine virtuelle Java, voir http://Java.Sun.com/javase/6/docs/api/Java/nio/charset/Charset.html

3
Thomas Lötzer

Mockito ne peut pas se moquer des dernières classes. JMock, combiné à une bibliothèque de JDave can. Voici les instructions .

JMock ne fait rien de spécial pour les classes finales, à part compter sur la bibliothèque JDave pour tout définir dans la machine virtuelle. Vous pouvez donc expérimenter avec l'infinaliseur de JDave et voir si Mockito va s'en moquer.

3
Yishai

Vous pouvez également utiliser l'extension Mockito de PowerMock pour simuler des classes/méthodes finales, même dans des classes système telles que String. Cependant, je vous conseillerais également de ne pas vous moquer de getBytes dans ce cas, mais d'essayer plutôt de configurer vos attentes de sorte qu'une chaîne réelle contenant les données attendues soit utilisée.

3
Johan

Peut-être que A.f (String) devrait être A.f (CharSequence) à la place. Vous pouvez vous moquer d'un CharSequence.

1
Chris Martin

Le projet exige que l'unité teste le pourcentage de couverture mais doit être supérieur à une valeur donnée. Pour atteindre ce pourcentage de couverture, les tests doivent couvrir le bloc catch relatif à l'exception UnsupportedEncodingException.

Quel est cet objectif de couverture? Certaines personnes diraient que tirer pour une couverture à 100% n'est pas toujours une bonne idée .

En outre, ce n'est pas une façon de vérifier si un bloc bloqué a été exercé. La bonne façon est d’écrire une méthode qui déclenche l’exception et de faire observer que l’exception est le critère de succès. Vous faites cela avec l'annotation @Test de JUnit en ajoutant la valeur "prévue":

@Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
   new ArrayList<Object>().get(1);
}
1
duffymo

Avez-vous essayé de passer un charsetName non valide à getBytes (String)?

Vous pouvez implémenter une méthode d'assistance pour obtenir le charsetName et remplacer cette méthode dans votre test par une valeur absurde.

1
Rich Seller

Vous pouvez changer votre méthode pour prendre l'interface CharSequence:

public final class A {
    public static String f(CharSequence str){
        try {
            return new String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // This is the catch block that I want to exercise.
            ...
        }
    }
}

De cette façon, vous pouvez toujours passer dans String, mais vous pouvez vous moquer de n'importe quelle façon.

0
tkruse

Si vous pouvez utiliser JMockit, regardez la réponse de Rogério.

Si et seulement si votre objectif est d'obtenir une couverture de code sans simuler réellement l'apparence de l'UTF-8 manquant au moment de l'exécution, vous pouvez procéder comme suit (et ne pas pouvoir ou ne pas utiliser JMockit):

public static String f(String str){
    return f(str, "UTF-8");
}

// package private for example
static String f(String str, String charsetName){
    try {
        return new String(str.getBytes(charsetName));
    } catch (UnsupportedEncodingException e) {
        throw new IllegalArgumentException("Unsupported encoding: " + charsetName, e);
    }
}

public class TestA {

    @Test(expected=IllegalArgumentException.class)
    public void testInvalid(){
        A.f(str, "This is not the encoding you are looking for!");
    }

    @Test
    public void testNormal(){
        // TODO do the normal tests with the method taking only 1 parameter
    }
}
0
Guillaume Perrot