PowerMock(+Mockito) +TestNG и имитация вызова (mock) статических методов

На хабре уже была статья с примерами использования PowerMock, но в ней не хватает такого описания, как имитации вызова статических методов как самостоятельных «единиц» в классе, так и в гибридном использовании, когда часть статических методов у класса подменяются «заглушкой», а часть вызываются реально. Попробую исправить эту нишу.

Для начала создадим демонстрационный класс со статическими методами (commit):

public class ClassStatic {
	static String getValue() {
		return "value";
	}

	static String getValue(final String s) {
		return getValue() + s;
	}
}


Добавим простое тестирование статичных методов. Для этого создадим класс ClassStaticTest (commit):
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

@Test(groups = {"static", "noMock"})
public class ClassStaticTest {
	@DataProvider
	public Object[][] data() throws Exception {
		return new Object[][]{{"", "value"}, {"test", "valuetest"}};
	}

	@Test
	public void testGetValueVoid() throws Exception {
		Assert.assertEquals(ClassStatic.getValue(), "value");
	}

	@Test(dataProvider = "data", dependsOnMethods = {"testGetValueVoid"})
	public void testGetValueAttribute(final String v, final String r) throws Exception {
		Assert.assertEquals(ClassStatic.getValue(v), r);
	}
}


До этого момента мы ни разу не использовали PowerMock, так как он нам не требовался. Теперь же, чтобы сделать «заглушку» для статических методов, создадим «костяк» тестового класса:
import org.powermock.modules.testng.PowerMockObjectFactory;
import org.testng.IObjectFactory;
import org.testng.annotations.ObjectFactory;
import org.testng.annotations.Test;

@Test(groups = {"static", "mock"})
public class ClassStaticMockTest {
	@ObjectFactory
	public IObjectFactory setObjectFactory() {
		return new PowerMockObjectFactory();
	}
}
Мы создали класс ClassStaticMockTest, в котором переопределили Object Factory для TestNG (документация)

Добавим тест с «заглушкой» для статического метода:
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockObjectFactory;
import org.testng.Assert;
import org.testng.IObjectFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.ObjectFactory;
import org.testng.annotations.Test;

import static org.mockito.MockitoAnnotations.Mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@Test(groups = {"static", "mock"})
@PrepareForTest({ClassStatic.class})
public class ClassStaticMockTest {

	@Mock
	public ClassStatic classStatic;

	@DataProvider
	public Object[][] data() throws Exception {
		return new Object[][]{{"", "newValue"}, {"test", "newValuetest"}};
	}

	@ObjectFactory
	public IObjectFactory setObjectFactory() {
		return new PowerMockObjectFactory();
	}

	@Test(dependsOnGroups = {"noMock"})
	public void mockGetValueVoid() throws Exception {
		mockStatic(ClassStatic.class);
		when(ClassStatic.getValue()).thenReturn("newValue");
		Assert.assertEquals(ClassStatic.getValue(), "newValue");
	}
}


Остался заключительный «аккорд» — гибридный тест, в котором вызов одного статического метод перекрыт «заглушкой», а для другого делается реальный вызов (commit):
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockObjectFactory;
import org.testng.Assert;
import org.testng.IObjectFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.ObjectFactory;
import org.testng.annotations.Test;

import static org.mockito.MockitoAnnotations.Mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

/** Created by borz on 06.07.13. */
@Test(groups = {"static", "mock"})
@PrepareForTest({ClassStatic.class})
public class ClassStaticMockTest {

	@Mock
	public ClassStatic classStatic;

	@DataProvider
	public Object[][] data() throws Exception {
		return new Object[][]{{"", "newValue"}, {"test", "newValuetest"}};
	}

	@ObjectFactory
	public IObjectFactory setObjectFactory() {
		return new PowerMockObjectFactory();
	}

	@Test(dependsOnGroups = {"noMock"})
	public void mockGetValueVoid() throws Exception {
		mockStatic(ClassStatic.class);
		when(ClassStatic.getValue()).thenReturn("newValue");
		Assert.assertEquals(ClassStatic.getValue(), "newValue");
	}

	@Test(dataProvider = "data", dependsOnMethods = {"mockGetValueVoid"})
	public void testGetValueAttribute(final String v, final String r) throws Exception {
		mockStatic(ClassStatic.class);
		when(ClassStatic.getValue()).thenReturn("newValue");
		when(ClassStatic.getValue(v)).thenCallRealMethod();
		Assert.assertEquals(ClassStatic.getValue(v), r);
	}
}
Мы перекрываем вызов метода getValue() и указываем, что при вызове getValue(String value) будет вызван реальный метод у класса, который уже внутри себя вызывает getValue().

Как «жизненный» пример потребности использования имитации вызова статических методов является «перекрытие» данных методов «заглушками» при тестировании других классов/методов (commit).

Добавляем класс, использующий у себя статический метод ClassStatic.getValue():
public class ClassUseStatic {
	public String getValue() {
		return ClassStatic.getValue() + "noStatic";
	}
}


И добавляем тест для метода ClassUseStatic.getValue() с переопределением вызова ClassStatic.getValue():
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockObjectFactory;
import org.testng.Assert;
import org.testng.IObjectFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.ObjectFactory;
import org.testng.annotations.Test;

import static org.mockito.MockitoAnnotations.Mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@Test(groups = {"useStatic", "mock"}, dependsOnGroups = {"static"})
@PrepareForTest({ClassStatic.class})
public class ClassUseStaticTest {
	@Mock
	public ClassStatic classStatic;

	@DataProvider
	public Object[][] data() throws Exception {
		return new Object[][]{{"newValue1", "newValue1noStatic"}, {"newValue2", "newValue2noStatic"}};
	}

	@ObjectFactory
	public IObjectFactory setObjectFactory() {
		return new PowerMockObjectFactory();
	}

	@Test(dataProvider = "data")
	public void testGetValue(String value, String result) throws Exception {
		mockStatic(ClassStatic.class);
		when(ClassStatic.getValue()).thenReturn(value);

		ClassUseStatic testClass = new ClassUseStatic();
		Assert.assertEquals(result, testClass.getValue());
	}
}


PS: Все примеры можно найти на GitHub.

UPD: добавил пример имитации вызова статических методов при тестировании «обычных» классов.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 4
  • +2
    Если для тестирования какого-то класса приходится изголяться, значит пора его переписать.
    • +1
      Тут нет изголения.
      Бывают моменты, когда требуется протестировать какой-то класс, «отключая» неявное тестирование другого класса.
      Добавил пример.
    • 0
      А как эта штука работает? Как удаётся переопределить вызовы статических методов?

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.