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

#java #mockito #powermock

#java #mockito #powermock

Вопрос:

Я ненавижу внедрять модульные тесты в устаревшую кодовую базу, но я должен.
До сих пор я успешно внедрял модульное тестирование в устаревшую кодовую базу, используя 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 равно null, они (c1, c2) не создаются повторно в вызовах подпоследовательности. Чего я не понимаю, так это почему reset (c1), reset (c2) не имеют эффекта в test2?

Есть идеи?

Комментарии:

1. Вы пробовали вручную сбросить значение c1 в null, используя отражение в методе @After?

2. Я попытался сбросить значение c1 на null в методе @Before, должен ли быть тот же эффект? В любом случае, я попробую @After. Кстати, производительность моих тестов стала крайне низкой после внедрения Mockito amp; PowerMock. Простой тест занимает более 10 минут.

3. @alpian: не повезло с @After, я попытался сбросить значения c1, c2 и sut на null в методе @After. Но из сеанса отладки я обнаружил, что перед test2 в конструкторе SUT значение c1 все еще НЕ равно null.

4. Что вы хотите протестировать, происходит с C1 и c2? Я бы, вероятно, не стал статически издеваться и просто издевался над c1 и c2, которые вы сделали, а затем установил их статически в моем устаревшем тестовом классе, используя отражение в самом методе тестирования. Я не завидую, что вам приходится иметь дело с этим отвратительным устаревшим кодом!

5. Кстати… у меня никогда не было проблем с производительностью с Mockito (но я не использовал PowerMock).

Ответ №1:

Наконец-то получилось. В принципе, я не могу установить заглушку (фиктивную статическую переменную экземпляра) в двух разных тестовых запусках. Я должен настроить ожидаемое поведение в первом @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?

Ответ №2:

Если вы используете Mockito для издевательства над статическими методами, вам необходимо либо

  1. Создайте статический макет в области try ( try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) { )
  2. Если вы создали статический макет вне области try, закройте статический макет после теста ( utilities.close() )

Комментарии:

1. Удивительно, что у этого нет положительных отзывов. Вызов close () был тем, что устранило проблему для меня, спасибо!

Ответ №3:

Попробуйте перенести вашу статическую фиктивную настройку в метод установки @BeforeClass, но оставьте вызов reset (mocks) в вашем методе test setup(). Вы хотите настроить свои 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
}