Mocking FacesContext
Я пытаюсь добавить некоторые модульные тесты в приложение JSF. Это приложение не опиралось в большой степени на лучшие практики, поэтому многие методы обслуживания используют FacesContext
получить данные из управляемых сессионных компонентов следующим образом:
(это внутри утилитарного класса)
public static Object getPageBean(String beanReference) {
FacesContext fc = FacesContext.getCurrentInstance();
VariableResolver vr = fc.getApplication().getVariableResolver();
return vr.resolveVariable(fc, beanReference);
}
Что было бы лучшим способом высмеять это? Я использую Groovy, поэтому у меня есть еще несколько вариантов для создания классов, которые я не могу обычно создавать.
7 ответов
В моем случае я смогла высмеять это в чистом виде. Я предоставляю карту MockBeans, которую он может вернуть:
private FacesContext getMockFacesContext(def map){
def fc = [
"getApplication": {
return ["getVariableResolver": {
return ["resolveVariable": { FacesContext fc, String name ->
return map[name]
}] as VariableResolver
}] as Application
},
"addMessage": {String key, FacesMessage val ->
println "added key: [${key}] value: [${val.getDetail()}] to JsfContext messages"
},
"getMessages": {return null}
] as FacesContext;
return fc;
}
Вы можете вернуть фиктивный контекст через FacesContext.getCurrentInstance
вызывая setCurrentInstance(FacesContext)
перед запуском теста. Метод защищен, но вы можете получить к нему доступ через отражение или расширение FacesContext
, Здесь приведен пример реализации с использованием Mockito.
Этот URL содержит действительно хорошую статью: http://illegalargumentexception.blogspot.com/2011/12/jsf-mocking-facescontext-for-unit-tests.html
У вас есть управляемый боб:
package foo;
import java.util.Map;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
@ManagedBean
@RequestScoped
public class AlphaBean {
public String incrementFoo() {
Map<String, Object> session = FacesContext.getCurrentInstance()
.getExternalContext()
.getSessionMap();
Integer foo = (Integer) session.get("foo");
foo = (foo == null) ? 1 : foo + 1;
session.put("foo", foo);
return null;
}
}
Вы заглушаете FacesContext:
package foo.test;
import javax.faces.context.FacesContext;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public abstract class ContextMocker extends FacesContext {
private ContextMocker() {
}
private static final Release RELEASE = new Release();
private static class Release implements Answer<Void> {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
setCurrentInstance(null);
return null;
}
}
public static FacesContext mockFacesContext() {
FacesContext context = Mockito.mock(FacesContext.class);
setCurrentInstance(context);
Mockito.doAnswer(RELEASE)
.when(context)
.release();
return context;
}
}
Затем напишите свой тестовый модуль:
@Test
public void testIncrementFoo() {
FacesContext context = ContextMocker.mockFacesContext();
try {
Map<String, Object> session = new HashMap<String, Object>();
ExternalContext ext = mock(ExternalContext.class);
when(ext.getSessionMap()).thenReturn(session);
when(context.getExternalContext()).thenReturn(ext);
AlphaBean bean = new AlphaBean();
bean.incrementFoo();
assertEquals(1, session.get("foo"));
bean.incrementFoo();
assertEquals(2, session.get("foo"));
} finally {
context.release();
}
}
Вы можете использовать, например, PowerMock, который является фреймворком, который позволяет расширять библиотеки-макеты, такие как Mockito, с дополнительными возможностями В этом случае это позволяет вам высмеивать статические методы FacesContext
,
Если вы используете Maven, используйте следующую ссылку, чтобы проверить необходимые настройки зависимостей.
Аннотируйте свой тестовый класс JUnit, используя эти две аннотации. Первая аннотация говорит JUnit запустить тест, используя PowerMockRunner
, Вторая аннотация говорит PowerMock подготовиться к издевательству над FacesContext
учебный класс.
@RunWith(PowerMockRunner.class)
@PrepareForTest({ FacesContext.class })
public class PageBeanTest {
издеваться FacesContext
используя PowerMock и использовать verify()
Мокито для того, чтобы проверить, что resolveVariable()
был вызван с ожидаемыми параметрами.
@Test
public void testGetPageBean() {
// mock all static methods of FacesContext
PowerMockito.mockStatic(FacesContext.class);
FacesContext facesContext = mock(FacesContext.class);
when(FacesContext.getCurrentInstance()).thenReturn(facesContext);
Application application = mock(Application.class);
when(facesContext.getApplication()).thenReturn(application);
VariableResolver variableResolver = mock(VariableResolver.class);
when(application.getVariableResolver()).thenReturn(variableResolver);
PageBean.getPageBean("bean_reference");
verify(variableResolver)
.resolveVariable(facesContext, "bean_reference");
}
Я создал пост в блоге, который объясняет приведенный выше пример кода более подробно.
Вот еще один способ использовать Mockito и рефлексию для насмешки FacesContext и чтобы убедиться, что обычные вызовы FacesContext.getCurrentInstance() возвращают (mocked) экземпляр, который вы хотите:
@Before
public void setUp() {
// Use Mockito to make our Mocked FacesContext look more like a real one
// while making it returns other Mocked objects
ExternalContext externalContext = Mockito.mock(ExternalContext.class);
Flash flash = Mockito.mock(Flash.class);
FacesContext facesContext = Mockito.mock(FacesContext.class);
Mockito.when(facesContext.getExternalContext()).thenReturn(externalContext);
Mockito.when(externalContext.getFlash()).thenReturn(flash);
// Use Java reflection to set the FacesContext to our Mock, since
// FacesContext.setCurrentInstance() is protected.
try {
Method setter = FacesContext.class.getDeclaredMethod("setCurrentInstance", new Class[]{FacesContext.class});
setter.setAccessible(true);
setter.invoke(null, new Object[]{facesContext});
} catch (Exception e) {
System.err.println("Exception in reflection-based access to FacesContext");
e.printStackTrace();
}
}
(Это адаптировано / расширено из ответа @McDowell ниже.)
Я привожу вам пример для насмешки FacesConext без использования PowerMockito. Идея состоит в том, чтобы расширить простой класс из Facescontext и изменить текущий экземпляр, используя защищенный статический метод setCurrentInstance:
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import com.sun.faces.config.InitFacesContext;
public class DummyTest {
@Mock
private FacesContext context;
@Before
public void before(){
MockitoAnnotations.initMocks(this);
ServletContext sc = mock(ServletContext.class);
new FakeContext(sc);
assertEquals(context, FacesContext.getCurrentInstance());
}
@Test
public void dummy(){
}
private class FakeContext extends InitFacesContext{
public FakeContext(ServletContext sc) {
super(sc);
setCurrentInstance(context);
}
}
}
Я считаю, что лучшее решение не представлено здесь. Вот так
@RunWith(PowerMockRunner.class)
@PrepareForTest({ FacesContext.class})
public class MyTestClass{
@Mock
private FacesContext facesContext;
@Before
public void init() throws Exception {
PowerMockito.mockStatic(FacesContext.class);
PowerMockito.when(FacesContext.getCurrentInstance()).thenReturn(facesContext);
}
И вам нужно импортировать весь пакет PowerMockito в ваш pom.xml
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>