Мокіто: Вставте реальні об’єкти в приватні @Autowired поля


191

Я використовую Mockito @Mockта @InjectMocksпримітки для введення залежностей у приватні поля, котрі помічені за допомогою Spring @Autowired:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Mock
    private SomeService service;

    @InjectMocks
    private Demo demo;

    /* ... */
}

і

public class Demo {

    @Autowired
    private SomeService service;

    /* ... */
}

Тепер я хотів би також вводити реальні об’єкти в приватні @Autowiredполя (без сетерів). Це можливо чи механізм обмежений лише введенням Mocks?


5
Зазвичай, коли ви знущаєтесь з речей, це означає, що ви не дуже дбаєте про конкретний предмет; що ви дійсно дбаєте про поведінку знущаного об’єкта. Можливо, замість цього ви хочете зробити інтеграційний тест? Або ви могли б надати обґрунтування того, чому ви хочете знущатися з конкретних предметів, які живуть разом?
Макото

2
Ну, я маю справу зі застарілим кодом, і це займе багато часу, коли (...). Тоді повернути (...) заяви, щоб встановити макет лише для запобігання деяких NPE тощо. З іншого боку, для цього можна безпечно використовувати реальний об'єкт. Тож було б дуже зручно мати варіант впорскування реальних об'єктів разом із макетами. Навіть якщо це може бути кодовий запах, я вважаю це розумним у цьому конкретному випадку.
користувач2286693

Не забувайте MockitoAnnotations.initMocks(this);про @Beforeметод. Я знаю, що це не пов'язане безпосередньо з оригінальним запитанням, але для тих, хто піде пізніше, що потрібно буде додати, щоб зробити це можливим.
Куга

7
@Cuga: якщо ви використовуєте бігун Mockito для JUnit, ( @RunWith(MockitoJUnitRunner.class)), вам не потрібна лініяMockitoAnnotations.initMocks(this);
Клінт Іствуд

1
Дякую ... Я ніколи цього не знав і завжди вказував і те, і інше
Куга

Відповіді:


306

Використовуйте @Spyпримітку

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Spy
    private SomeService service = new RealServiceImpl();

    @InjectMocks
    private Demo demo;

    /* ... */
}

Mockito буде розглядати всі поля, що мають @Mockабо @Spyпримітку, як потенційних кандидатів, які будуть введені в екземпляр, позначений @InjectMocksанотацією. У цьому випадку 'RealServiceImpl'екземпляр буде введений у "демонстрацію"

Детальніше див

Мокіто-дім

@Spy

@Mock


9
+1: працював для мене ... крім об'єктів String. Мокіто скаржиться:Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
Адріан Пронк

Дякую Це працювало і для мене :) Щоб змусити проксі використовувати mock else для реального використання, використовуйте шпигуна
Swarit Agarwal

Дякую! Саме це мені і потрібно!
nterry

2
У моєму випадку Mockito не вводить шпигуна. Він все-таки вводить макет. Поле приватне та без сетера.
Vituel

8
BTW, в цьому немає необхідності new RealServiceImpl(), @Spy private SomeService service;створює реальний об'єкт за допомогою конструктора за замовчуванням, перш ніж шпигувати за ним.
parxier

20

На додаток до відповіді @Dev Blanked, якщо ви хочете використовувати існуючий боб, створений Spring, код можна змінити на:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {

    @Inject
    private ApplicationContext ctx;

    @Spy
    private SomeService service;

    @InjectMocks
    private Demo demo;

    @Before
    public void setUp(){
        service = ctx.getBean(SomeService.class);
    }

    /* ... */
}

Таким чином, вам не потрібно змінювати код (додайте інший конструктор) лише для того, щоб тести спрацювали.


2
@Aada Чи можете ви детальніше?
Йоаз Менда

1
З якої бібліотеки це походить, я бачу лише InjectMocks в org.mockito
Sameer

1
@ імпорт імпорту org.mockito.InjectMocks;
Йоаз Менда

Додайте коментар, щоб скасувати Aada. Це працювало для мене. Я не міг "просто не використовувати введення поля", як пропонують інші відповіді, тому мені довелося переконатися, що Autowiredполя із залежностей створені правильно.
Scrambo

1
Я спробував це. але я отримую нульовий вказівник виключення з методу настройки
Ахіл Сурапурам

3

Mockito не є рамкою DI, і навіть рамки DI заохочують конструкторські ін'єкції над полевими інжекціями.
Отже, ви просто оголосите конструктор для встановлення залежностей тестуваного класу:

@Mock
private SomeService serviceMock;

private Demo demo;

/* ... */
@BeforeEach
public void beforeEach(){
   demo = new Demo(serviceMock);
}

Використання Mockito spyдля загального випадку - жахлива порада . Це робить тестовий клас крихким, не прямим та схильним до помилок: Що насправді насміхається? Що насправді тестується?
@InjectMocksа @Spyтакож шкодить загальному дизайну, оскільки він заохочує роздуті класи та змішану відповідальність у класах.
Будь ласка, прочитайте spy()javadoc перед тим, як використовувати сліпо (акцент не мій):

Створює шпигун реального об'єкта. Шпигун називає реальні методи, якщо вони не заглушені. Справжніх шпигунів слід використовувати обережно та епізодично , наприклад, при роботі зі спадковим кодом.

Як завжди, ви збираєтеся читати partial mock warning: Об'єктно-орієнтоване програмування вирішує складність, поділяючи складність на окремі, конкретні, SRPy-об'єкти. Як часткова насмішка вписується в цю парадигму? Ну, це просто не так ... Частковий макет зазвичай означає, що складність була перенесена на інший метод на одному об’єкті. У більшості випадків це не так, як ви хочете розробити свою програму.

Однак трапляються нечасті випадки, коли часткові макети стають під рукою: мати справу з кодом, яку ви не можете легко змінити (сторонні інтерфейси, тимчасовий рефакторинг застарілого коду тощо). Однак я б не використовував часткові макети для нових, керованих тестом і розроблений код.


0

Навесні існує спеціальна утиліта, яка називається ReflectionTestUtilsдля цієї мети. Візьміть конкретний екземпляр і введіть у поле.


@Spy
..
@Mock
..

@InjectMock
Foo foo;

@BeforeEach
void _before(){
   ReflectionTestUtils.setField(foo,"bar", new BarImpl());// `bar` is private field
}

-1

Я знаю, що це давнє питання, але ми зіткнулися з тією ж проблемою при спробі ввести струни. Отже, ми винайшли розширення JUnit5 / Mockito, яке робить саме те, що ви хочете: https://github.com/exabrial/mockito-object-injection

Редагувати:

@InjectionMap
 private Map<String, Object> injectionMap = new HashMap<>();

 @BeforeEach
 public void beforeEach() throws Exception {
  injectionMap.put("securityEnabled", Boolean.TRUE);
 }

 @AfterEach
 public void afterEach() throws Exception {
  injectionMap.clear();
 }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.