Mockito/PowerMock: как сбросить фиктивную статическую переменную в SUT?

Я ненавижу внедрять модульные тесты в унаследованную кодовую базу, но я должен это сделать.
До сих пор я успешно внедрял модульное тестирование в устаревшую кодовую базу, используя Mockito и PowerMock. Работал отлично, пока не столкнулся с такой ситуацией:

в SUT есть несколько статических переменных (которые я имитировал с помощью PowerMock, имитируя статические методы и конструкторы).
Теперь в первом тестовом методе все работало нормально, и фиктивная статическая переменная вернула ожидаемое выходное значение.
но в последующих тестовых методах фиктивный статический объект всегда возвращает значение, которое было установлено в первом тесте, хотя я вызывал для него reset() перед тестами.

// legacy code base:
public class SUT {
  private static Collaborator1 c1 = null;
  private static Collaborator2 c2 = null;

  public SUT(param1) {
    if (c1 == null) {
        c1 = Collaborator1.instance(param1);
        c2 = new Collaborator2(c1);
    } else {
    }
  }
}



// newly introduced unit tests:
@RunWith(PowerMockRunner.class)
@PrepareForTest({
    SUT.class,                  // to mock: new Collaborator2(..), as required by PowerMock when mocking constructors
    Collaborator1.class,        // to mock: Collaborator1.instance(..), as required by PowerMock in mocking static methods
})
public class SUTTest {

  private SUT sut;

  private Collaborator1 c1 = mock(Collaborator1.class);
  private Collaborator2 c2 = mock(Collaborator2.class);

  @Before  
  public void setup() {
    // mock c1:
    PowerMockito.mockStatic(Collaborator1.class);
    when(Collaborator1.instance(param1)).thenReturn(c1);

    // mock c2:
    PowerMockito.whenNew(Collaborator2.class).withArguments(c1).thenReturn(c2);

    reset(c1);
    reset(c2);

    sut = new SUT(param1);
  }

  @Test
  public void test1() {
    when(c2.foo(input1)).thenReturn(out1); 

    // do something
  }

  @Test
  public void test2() {
    when(c2.foo(input2)).thenReturn(out2);    // BANG!!! c2.foo(input2) always return "out1"

    // do something
  }
}



Поскольку конструктор SUT создает экземпляры c1 и c2 только в том случае, если статический c1 равен нулю, они (c1, c2) не создаются повторно в вызовах подпоследовательности. Чего я не понимаю, так это почему сброс (c1), сброс (c2) не действуют в test2?

Есть идеи?


person Tumer    schedule 26.04.2011    source источник
comment
Вы пытались вручную сбросить c1 до нуля, используя Reflection в методе @After?   -  person alpian    schedule 26.04.2011
comment
Я попытался сбросить c1 до нуля в методе @Before, должен ли быть тот же эффект? В любом случае, я попробую @After. Кстати, производительность моих тестов стала крайне низкой после введения Mockito и PowerMock. Простой тест занимает более 10 минут.   -  person Tumer    schedule 26.04.2011
comment
@alpian: не повезло с @After, я попытался сбросить значения c1, c2 и sut до нуля в методе @After. Но из сеанса отладки я обнаружил, что перед test2 в конструкторе SUT c1 по-прежнему НЕ равен нулю.   -  person Tumer    schedule 26.04.2011
comment
Что вы хотите проверить, происходит с C1 и c2? Я бы, вероятно, исключил статическое издевательство и просто издевался над c1 и c2, которые вы сделали, а затем установил их статически в моем устаревшем тестовом классе, используя отражение в самом методе тестирования. Я не завидую, что вам приходится иметь дело с этим неприятным устаревшим кодом!   -  person alpian    schedule 26.04.2011
comment
Кстати... у меня никогда не было проблем с производительностью с Mockito (но я не использовал PowerMock).   -  person alpian    schedule 26.04.2011


Ответы (2)


Получил работу наконец. По сути, я не могу установить заглушку (имитируемую статическую переменную экземпляра) в двух разных тестовых прогонах. Я должен настроить ожидаемое поведение в первом @Before.
Вместо использования

  @Before  
  public void setup() {
    ...
  }

  @Test
  public void test1() {
    when(c2.foo(input1)).thenReturn(out1); 
  }

  @Test
  public void test2() {
    when(c2.foo(input2)).thenReturn(out2); 
  }

Я должен использовать эту последовательность:

@Before  
  public void setup() {
    when(c2.foo(input1)).thenReturn(out1); 
    when(c2.foo(input2)).thenReturn(out2);
  }

  @Test
  public void test1() {
    // do something
  }

  @Test
  public void test2() {
    // do something
  }

Некоторое ограничение (ошибка?) в PowerMock/Mockito?

person Tumer    schedule 27.04.2011

Попробуйте переместить статическую макетную установку в метод установки @BeforeClass, но оставьте вызов reset(mocks) в методе тестовой настройки(). Вы хотите настроить mockStatic только один раз, но, поскольку они статичны, вам нужно будет сбрасывать их для каждого теста, иначе они будут путаться с последующими тестами.

то есть попробовать

@BeforeClass  
public void setupClass() {
    // mock c1:
    PowerMockito.mockStatic(Collaborator1.class);
    when(Collaborator1.instance(param1)).thenReturn(c1);

    // mock c2:
    PowerMockito.whenNew(Collaborator2.class).withArguments(c1).thenReturn(c2);
}

@Before  
public void setup() {
  reset(c1);
  reset(c2);

  sut = new SUT(param1);
}

@Test
public void test1() {
  // do something
}

...

person VinnyQ77    schedule 01.04.2016