Подтвердить что ты не робот

Как издеваться над String с помощью mockito?

Мне нужно смоделировать тестовый сценарий, в котором я вызываю метод getBytes() объекта String, и я получаю UnsupportedEncodingException.

Я пытался добиться этого, используя следующий код:

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

Проблема в том, что когда я запускаю свой тестовый сценарий, я получаю исключение MockitoException, в котором говорится, что я не могу издеваться над классом java.lang.String.

Есть ли способ обмануть объект String с помощью mockito или, альтернативно, способ заставить объект String вызывать исключение UnsupportedEncodingException при вызове метода getBytes?


Вот более подробная информация, чтобы проиллюстрировать проблему:

Это класс, который я хочу проверить:

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.
            ...
        }
    }
}

Это мой класс тестирования (я использую JUnit 4 и 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);
    }
}
4b9b3361

Ответ 1

Проблема в том, что класс String в Java отмечен как окончательный, поэтому вы не можете издеваться над традиционными фальшивыми фреймворками. Согласно FAQ по Mockito, это ограничение также и для этой структуры.

Ответ 2

Как просто создать String с неправильным именем кодировки? См

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

Mocking String - почти наверняка плохая идея.

Ответ 3

Если все, что вы собираетесь делать в своем блоке catch, выбрасывает исключение во время выполнения, вы можете сэкономить на некоторых вводах, просто используя объект Charset, чтобы указать свое имя набора символов.

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

Таким образом, вы не поймаете исключение, которое никогда не произойдет только потому, что компилятор вам говорит.

Ответ 4

Как указывали другие, вы не можете использовать Mockito для издевательства над последним классом. Тем не менее, более важным моментом является то, что тест не особенно полезен, поскольку он просто демонстрирует, что String.getBytes() может генерировать исключение, что, очевидно, может быть сделано. Если вы решительно настроитесь на тестирование этой функции, я думаю, вы могли бы добавить параметр для кодирования в f() и отправить в тест плохую ценность.

Кроме того, вы вызываете ту же проблему для вызывающего пользователя A.f(), потому что A является окончательным, а f() является статическим.

Эта статья может быть полезной для убеждения ваших коллег быть менее догматичными в отношении покрытия 100% кода: Как провалиться со 100% -ным охватом тестирования.

Ответ 5

В своей документации JDave не может удалить "окончательные" модификаторы из классов, загружаемых загрузчиком загрузки bootstrap. Это включает все классы JRE (из java.lang, java.util и т.д.).

Инструмент, который позволяет вам издеваться над чем-либо, JMockit.

С JMockit ваш тест может быть записан как:

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");
   }
}

предполагая, что полный класс "A":

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);
      }
   }
}

Я действительно выполнил этот тест на своей машине. (Обратите внимание, что я обернул исходное исключенное исключение в исключении времени выполнения.)

Я использовал частичное издевательство над @Mocked("getBytes"), чтобы JMockit не издевался над всем в классе java.lang.String (просто представьте, что это может вызвать).

Теперь этот тест действительно не нужен, потому что "UTF-8" - это стандартная кодировка, необходимая для поддержки во всех JRE. Поэтому в производственной среде блок catch никогда не будет выполнен.

"Необходимость" или желание покрыть блокирующий блок все еще действительны. Итак, как избавиться от теста, не уменьшая процент покрытия? Вот моя идея: вставьте строку с assert false; в качестве первого выражения внутри блока catch и попросите инструмент Cover Coverage игнорировать весь блок catch, когда сообщается о показателях охвата. Это один из моих "предметов TODO" для JMockit Coverage. 8 ^)

Ответ 6

Мокито не может высмеять финальные классы. JMock, в сочетании с библиотекой JDave. Вот инструкции.

JMock не делает ничего особенного для финальных классов, кроме как полагаться на библиотеку JDave, чтобы отменить все в JVM, чтобы вы могли поэкспериментировать с использованием Uninalizer JDave и посмотреть, будет ли Mockito его издеваться.

Ответ 7

Вы также можете использовать расширение PowerMock Mockito для моделирования окончательных классов/методов даже в системных классах, таких как String. Тем не менее, я бы также советовал против насмешек getBytes в этом случае и скорее попытался настроить ваше ожидание, чтобы вместо этого использовалась строка, заполненная ожидаемыми данными.

Ответ 9

Это требование к проекту, согласно которому процент тестируемого объекта должен превышать заданное значение. Чтобы достичь такого процента охвата, тесты должны охватывать блок catch относительно исключения UnsupportedEncodingException.

Что это за цель? Некоторые люди скажут, что стрельба для покрытия 100% не всегда хорошая идея.

Кроме того, это не способ проверить, был ли реализован блок catch. Правильный способ - написать метод, который заставляет исключение быть брошенным и сделать наблюдение за исключением, которое бросает критерий успеха. Вы делаете это с помощью аннотации JUnit @Test, добавляя "ожидаемое" значение:

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

Ответ 10

Вы пытались передать недопустимое имя charsetName в getBytes (String)?

Вы можете реализовать вспомогательный метод для получения charsetName и переопределить этот метод в вашем тесте на бессмысленное значение.

Ответ 11

Возможно, A.f(String) должен быть A.f(CharSequence). Вы можете издеваться над CharSequence.

Ответ 12

Если вы можете использовать JMockit, посмотрите на ответ Рожерио.

Если и только если ваша цель состоит в том, чтобы получить покрытие кода, но не реально имитировать то, что будет выглядеть UTF-8 во время выполнения, вы можете сделать следующее (и что вы не можете или не хотите использовать 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
    }
}

Ответ 13

Вы можете изменить свой метод, чтобы взять интерфейс 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.
            ...
        }
    }
}

Таким образом, вы все равно можете пройти в String, но вы можете издеваться над любым способом.

Ответ 14

Если у вас есть блок кода, который никогда не может быть запущен, и требования к управлению, чтобы иметь 100% -ный охват тестирования, тогда что-то изменится.

Что вы можете сделать, так это сделать кодировку символов переменной-членом и добавить в свой класс конструктор private-private, который позволяет передать его. В unit test вы можете вызвать новый конструктор с бессмысленным значением для кодировки символов.