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

@InjectMocks ведет себя по-разному с Java 6 и 7

С очень простым Mockito запускают JUnit test и класс. Я вижу другой вывод, когда тест выполняется с Java 1.6.0_32 и Java 1.7.0_04 и хочет понять, почему это происходит. Я подозреваю, что происходит стирание какого-то типа, но хотелось бы получить окончательный ответ.

Вот мой примерный код и инструкции о том, как запустить из командной строки:

FooServiceTest.java

import org.junit.*;
import org.junit.runner.*;
import org.mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.*;
import java.util.*;

@RunWith(MockitoJUnitRunner.class)
public class FooServiceTest {
  @Mock Map<String, String> mockStringString;
  @Mock Map<String, Integer> mockStringInteger;

  @InjectMocks FooService fooService;

  public static void main(String[] args) {
    new JUnitCore().run(FooServiceTest.class);
  }

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void checkInjection() {
    when(mockStringString.get("foo")).thenReturn("bar");
    fooService.println();
  }
}

FooService.java

import java.util.*;

public class FooService {
  private Map<String, String> stringString = new HashMap<String, String>();
  private Map<String, Integer> stringInteger = new HashMap<String, Integer>();

  public void println() {
    System.out.println(stringString.get("foo") + " " + stringInteger);
  }
}

Чтобы скомпилировать и запустить этот пример:

  • сохранить приведенное выше в файлах
  • загрузите и поместите в тот же каталог junit.4.10.jar и mockito-all-1.9.0.jar
  • установить PATH для включения JDK
  • скомпилировать с javac -cp junit-4.10.jar;mockito-all-1.9.0.jar *.java
  • запустить с java -cp .;junit-4.10.jar;mockito-all-1.9.0.jar FooServiceTest

Я полагаю, что вывод выше null {}, потому что @InjectMocks инъекция полей не может корректно разрешать типы, так как они являются типами Map. Правильно ли это?

Теперь изменение одного из макетных имен в соответствии с полем класса должно позволить Mockito найти совпадение. Например, изменение

@Mock Map<String, Integer> mockStringInteger;

к

@Mock Map<String, Integer> stringInteger;

тогда компиляция/работа с Java 1.6.0_32 дает (IMHO ожидаемый) вывод bar stringInteger, но с 1.7.0_04 дает null stringInteger.

Вот как я его запускаю (из командной строки в Windows 7):

E:\src\mockito-test>set PATH="C:\Program Files (x86)\Java\jdk1.6.0_32\bin"
E:\src\mockito-test>javac -cp junit-4.10.jar;mockito-all-1.9.0.jar *.java
E:\src\mockito-test>java -cp .;junit-4.10.jar;mockito-all-1.9.0.jar FooServiceTest
    bar stringInteger
E:\src\mockito-test>set PATH="C:\Program Files (x86)\Java\jdk1.7.0_04\bin"
E:\src\mockito-test>javac -cp junit-4.10.jar;mockito-all-1.9.0.jar *.java
E:\src\mockito-test>java -cp .;junit-4.10.jar;mockito-all-1.9.0.jar FooServiceTest
    null stringInteger
4b9b3361

Ответ 1

Я полагаю, что вывод выше: я null {}, потому что инъекция поля @InjectMocks не может корректно разрешать типы, так как они являются типами Map. Правильно ли это?

Да, правильно в этом поле Mockito не может устранить двусмысленность, поэтому он просто игнорирует эти неоднозначные поля.

При очень простом тестировании Mockito JUnit и классе я вижу другой вывод, когда тест выполняется с Java 1.6.0_32 и Java 1.7.0_04 и хочет понять, почему это происходит.

На самом деле разница отличается от другого типа Arrays.sort и, следовательно, Collections.sort() между JDK 6 и JDK 7. Разница заключается в новом алгоритме, который должен выполнять на 20% меньше свопов. Вероятно, это операция смены, которая заставляла работать под JDK6 и JDK7.

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

Большое спасибо за сообщение об этом нечетном поведении, я создал проблему на Mockito, однако на данный момент я действительно не буду решать этот вопрос, а скорее обеспечит одинаковое поведение в JDK. Для решения этой ситуации может потребоваться кодировать новый алгоритм при сохранении совместимости, в то же время вы должны называть все свои полевые mocks соответствующим полям тестируемого класса.

В настоящее время, вероятно, нужно будет настроить компаратора с дополнительными сравнениями для обеспечения того же порядка на JDK6 и JDK7. Плюс добавление некоторого предупреждения в Javadoc.

РЕДАКТИРОВАТЬ. Выполнение двух проходов может решить проблему для большинства людей.

Надеюсь, что это поможет. спасибо для определения проблемы.


Также вам нужно либо MockitoAnnotations.initMocks(this);, либо бегун @RunWith(MockitoJUnitRunner.class), использование обоих не является необходимым, и может даже вызвать некоторые проблемы.:)

Ответ 2

Поведение Mockito undefined, если имеется более одного макета, который соответствует одному из полей, которые будут введены. Здесь "match" означает правильный тип, игнорируя любые параметры типа - стирание типа не позволяет Mockito знать о параметрах типа. Таким образом, в вашем примере любой из двух mocks может быть введен в одно из двух полей.

Тот факт, что вам удалось наблюдать за другим поведением с Java 6 от Java 7, является немного красной селедки. В любой версии Java нет причин ожидать, что Mockito будет правильно выбирать между mockStringString или mockStringInteger, для одного из двух полей, которые он вводит.

Ответ 3

Правильно ли это?

В самом деле, из-за типа стирания, Mockito не может видеть разницу между различными Картами во время выполнения/через отражение, которое дает Мокито тяжело делает правильную инъекцию.

Ответ null на stringString.get("foo") может иметь две причины:

  • stringString должным образом высмеивается, но обрезание не происходит (get всегда возвращает null).
  • stringString не издевается, поэтому еще HashMap без значения для "foo", поэтому get вернет null.

Ответ {} на переменную stringInteger означает, что он был инициализирован (в классе) с фактическим (пустым) HashMap.

Так что ваш вывод говорит вам, что stringInteger не издевается. Что насчет stringString?

Если ни один из двух @Mock не имеет имен, которые соответствуют любому из полей тестируемого класса, ничего не издевается. Причина? Я подозреваю, что он не может решить, в какую область вводить, поэтому он не делает насмешек. Вы можете проверить это, показывая обе переменные, которые будут давать {}. Это объясняет ваше значение null.

Если одно из @Mock имеет имя, которое соответствует, а другое имеет тот, который этого не делает (ваша модификация, одна mockedStringString и одна stringInteger, то есть, с тем же именем), что должно Mockito делать?

То, что вы хотите сделать, это ввести только один из них и только в поле с соответствующим именем. В вашем случае у вас есть mockedStringString (вы ожидаете, что это не соответствует) и stringInteger (вы ожидаете, что оно будет соответствовать). Поскольку вы заглушили mockedStringString (!), Который не будет соответствовать, ожидаемый результат будет null.

Другими словами, я думаю, что ответ Java 7 в порядке, а для Java 6 один не подходит для конкретного примера.

Чтобы узнать, что происходит для (неожиданного) поведения, которое вы получаете для Java 6, попробуйте иметь только один @Mock - если я правильно высмеиваю stringString и не имею никакого макета для stringInteger, макет для for stringString вводится в поле stringInteger. Другими словами, Mockito, кажется, сначала выясняет, что он может впрыснуть (учитывая имя), а затем вводит макет в одну из подходящих возможностей (но не обязательно правильный).