JSFアプリケーションにいくつかの単体テストを追加しようとしています。このアプリケーションはベストプラクティスに大きく依存していなかったため、多くのサービスメソッドはFacesContext
を使用して次のようにマネージドセッションBeanからデータをプルします。
(これはutilクラス内にあります)
public static Object getPageBean(String beanReference) {
FacesContext fc = FacesContext.getCurrentInstance();
VariableResolver vr = fc.getApplication().getVariableResolver();
return vr.resolveVariable(fc, beanReference);
}
これをモックする最良の方法は何でしょうか?私はgroovyを使用しているので、通常は作成できないクラスを作成するためのオプションがいくつかあります。
私の場合、純粋なグルーヴィーでそれをモックすることができました。私はそれが返すことができる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;
}
テストを実行する前に setCurrentInstance(FacesContext)
を呼び出すことにより、FacesContext.getCurrentInstance
を介してモックコンテキストを返すことができます。メソッドは保護されていますが、リフレクションを介して、またはFacesContext
を拡張することでアクセスできます。 Mockito here を使用したサンプル実装があります。
このURLはそれに関する本当に良い記事を提供します: http://illegalargumentexception.blogspot.com/2011/12/jsf-mocking-facescontext-for-unit-tests.html
管理対象のBeanがあります。
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を使用している場合は、次の link を使用して、必要な依存関係の設定を確認してください。
これらの2つのアノテーションを使用して、JUnitテストクラスにアノテーションを付けます。最初のアノテーションは、PowerMockRunner
を使用してテストを実行するようにJUnitに指示します。 2番目のアノテーションは、PowerMockにFacesContext
クラスをモックする準備をするように指示します。
_@RunWith(PowerMockRunner.class)
@PrepareForTest({ FacesContext.class })
public class PageBeanTest {
_
PowerMockを使用してFacesContext
をモックし、Mockitoの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");
}
_
上記のコードサンプルをより詳細に説明する ブログ投稿 を作成しました。
PowerMockitoを使用せずにFacesConextをモックする例を示します。アイデアは、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);
}
}
}
Mockitoとリフレクションを使用してFacesContextをモックし、FacesContext.getCurrentInstance()への通常の呼び出しが必要な(モックされた)インスタンスを返すようにする別の方法を次に示します。
@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の回答から適応/拡張されています。)
ここでは最善の解決策は提示されていないと思います。さあ行こう
@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);
}
そして、pom.xmlにすべてのPowerMockitoバンドルをインポートする必要があります
<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>