Модульное тестирование с mockito для конструкторов

У меня один класс.

Class First {

    private Second second;

    public First(int num, String str) {
        second = new Second(str);
        this.num = num;
    }

    ... // some other methods
}

Я хочу написать модульные тесты для общедоступных методов класса First. Я хочу избежать выполнения конструктора класса Second.

Я сделал это:

Second second = Mockito.mock(Second.class);
Mockito.when(new Second(any(String.class))).thenReturn(null);
First first = new First(null, null);

Он по-прежнему вызывает конструктор класса Second. Как я могу этого избежать?


person Tarun Kumar    schedule 26.06.2012    source источник


Ответы (7)


Еще раз проблема с модульным тестированием возникает из-за ручного создания объектов с использованием оператора new. Вместо этого рассмотрите возможность передачи уже созданного Second:

class First {

  private Second second;

  public First(int num, Second second) {
    this.second = second;
    this.num = num;
  }

  // some other methods...
}

Я знаю, что это может означать серьезную переработку вашего API, но другого пути нет. Также этот класс не имеет никакого смысла:

Mockito.when(new Second(any(String.class).thenReturn(null)));

Прежде всего, Mockito может только имитировать методы, а не конструкторы. Во-вторых, даже если бы вы могли издеваться над конструктором, вы издевались над конструктором только что созданного объекта и никогда ничего не делали с этим объектом.

person Tomasz Nurkiewicz    schedule 26.06.2012
comment
Но есть другой способ: добавьте PowerMock в смесь, чтобы вы могли издеваться над конструктором Second, как показывает ответ @terma. - person Rogério; 09.07.2012
comment
@Rogério Я согласен и не согласен. Я согласен в том смысле, что это жизнеспособная альтернатива, но я не согласен, потому что OP использует только Mockito и не указывает на желание или даже возможность получить и использовать отдельную структуру, которая менее тривиальна в корпоративной среде, чем это находится в академической. Это все еще должен быть принятый ответ, основанный на том, как ОП сформулировал свой вопрос. Кроме того, к первому пункту, который Томаш сделал в своем ответе, Mockito написан для продвижения написания кода, который можно тестировать. Использование Powermock указывает на то, что вы написали непроверяемый код, и он нуждается в рефакторинге. - person searchengine27; 31.10.2017
comment
@ searchengine27 На самом деле, нигде в вопросе не говорится, что добавление еще одной библиотеки тестирования будет проблемой (конечно, может быть). По моему опыту работы с реальными проектами, добавление библиотеки test не представляет большой проблемы, поскольку она используется только для автоматизированных тестов, а не для производственного кода. И Mockito был не написан для продвижения написания кода, который можно тестировать — это всего лишь ваше мнение, для которого нет никаких доказательств; дайте мне ссылку, если вы думаете, что есть. Наоборот, использование тестовых библиотек с такими произвольными (и случайными) ограничениями часто препятствует правильному проектированию API и объектно-ориентированного кода. - person Rogério; 01.11.2017
comment
Есть еще один способ. Он может переместить логику создания объекта в метод и смоделировать/переопределить этот метод для тестирования. - person Muhammad Gelbana; 26.12.2018
comment
Итак, если бы в конструкторе был вызов метода для создания экземпляра Second, как его можно было бы имитировать? - person KumarAnkit; 10.01.2019

Вы можете использовать PowerMockito.

см. пример:

Second second = Mockito.mock(Second.class);
whenNew(Second.class).withNoArguments().thenReturn(second);

Но рефакторинг - лучшее решение.

person terma    schedule 26.06.2012
comment
Уведомление о покрытии: если тестируемый класс (A) создает новый экземпляр другого класса (B), вам нужно будет добавить A в @PrepareForTest для вызова whenNew() в этом случае И этого не позволит Jacoco прочитать репортаж. - person Abhishek Saini; 22.02.2018

Вот код для имитации этой функции с помощью API PowerMockito.

Second mockedSecond = PowerMockito.mock(Second.class);
PowerMockito.whenNew(Second.class).withNoArguments().thenReturn(mockedSecond);

Вам нужно использовать Powermockito runner и добавить необходимые тестовые классы (разделенные запятыми ), которые должны быть имитированы API powermock .

@RunWith(PowerMockRunner.class)
@PrepareForTest({First.class,Second.class})
class TestClassName{
    // your testing code
}
person Rocky Mena    schedule 20.11.2015

Я использовал "Шаблон 2 -" заводской вспомогательный шаблон"

Шаблон 2 — шаблон фабричного помощника

Один из случаев, когда этот шаблон не будет работать, — это если MyClass является окончательным. Большая часть фреймворка Mockito не очень хорошо работает с конечными классами; и это включает использование spy(). Другой случай, когда MyClass где-то использует getClass() и требует, чтобы результирующее значение было MyClass. Это не сработает, потому что класс шпиона на самом деле является сгенерированным Mockito подклассом исходного класса.

В любом из этих случаев вам понадобится немного более надежный шаблон помощника фабрики, как показано ниже.

public class MyClass{
  static class FactoryHelper{
      Foo makeFoo( A a, B b, C c ){
          return new Foo( a, b, c );
      }
  }

  //...

  private FactoryHelper helper;
  public MyClass( X x, Y y ){
      this( x, y, new FactoryHelper());
  } 

  MyClass( X x, Y, y, FactoryHelper helper ){

      //...

      this.helper = helper;
  } 

  //...

  Foo foo = helper.makeFoo( a, b, c );
}

Итак, у вас есть специальный конструктор, предназначенный только для тестирования, с дополнительным аргументом. Это используется из вашего тестового класса при создании объекта, который вы собираетесь тестировать. В своем тестовом классе вы издеваетесь над классом FactoryHelper, а также над объектом, который хотите создать.

@Mock private MyClass.FactoryHelper mockFactoryHelper;
@Mock private Foo mockFoo;
private MyClass toTest;

и вы можете использовать его так

toTest = new MyClass( x, y, mockFactoryHelper ); 
when( mockFactoryHelper.makeFoo( 
  any( A.class ), any( B.class ), any( C.class )))
  .thenReturn( mockFoo ); 

Источник: http://web.archive.org/web/20160322155004/http://code.google.com/p/mockito/wiki/MockingObjectCreation

person Velibor Dragutinović    schedule 21.01.2014
comment
Я также заставил работать шаблон 2, однако я думаю, что в этой документации есть важная опечатка. См. мой комментарий к code.google.com/p/mockito/wiki/MockingObjectCreation. - person Michael Osofsky; 04.12.2014
comment
Круто, я реализовал фабричный шаблон, и это решило мой другой вопрос. stackoverflow.com/q/47445870/3805770 - person KingKongCoder; 23.11.2017
comment
Я реализовал этот шаблон. Но теперь я застрял в том, как написать тестовый пример для класса Factory. - person v1shnu; 19.01.2021

Mockito теперь может имитировать конструкторы (начиная с версии 3.5.0) https://javadoc.io/static/org.mockito/mockito-core/3.5.13/org/mockito/Mockito.html#mocked_construction

try (MockedConstruction mocked = mockConstruction(Foo.class)) {
   Foo foo = new Foo();
   when(foo.method()).thenReturn("bar");
   assertEquals("bar", foo.method());
   verify(foo).method();
 }
person distante    schedule 30.09.2020

Я считаю, что невозможно издеваться над конструкторами, используя mockito. Вместо этого я предлагаю следующий подход

Class First {

   private Second second;

   public First(int num, String str) {
     if(second== null)
     {
       //when junit runs, you get the mocked object(not null), hence don't 
       //initialize            
       second = new Second(str);
     }
     this.num = num;
   }

   ... // some other methods
}

И, для теста:

class TestFirst{
    @InjectMock
    First first;//inject mock the real testable class
    @Mock
    Second second

    testMethod(){

        //now you can play around with any method of the Second class using its 
        //mocked object(second),like:
        when(second.getSomething(String.class)).thenReturn(null);
    }
}
person Rishabh Sachdeva    schedule 06.03.2018
comment
это действующий образец? Сонар будет жаловаться на поле доступа Second перед инициализацией. Если это не окончательно, это можно обойти, но все же, это рекомендуемый шаблон? - person JrBenito; 25.03.2020

Включите эту строку поверх вашего тестового класса

@PrepareForTest({ First.class })
person Ramesh V    schedule 23.03.2020