【问题标题】:Mocking FacesContextMocking FacesContext
【发布时间】:2010-11-23 02:57:41
【问题描述】:

我正在尝试向 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,所以我有更多选项来创建我通常无法创建的类。

【问题讨论】:

    标签: unit-testing jsf groovy mocking facescontext


    【解决方案1】:

    您可以在运行测试之前调用setCurrentInstance(FacesContext),通过FacesContext.getCurrentInstance 返回一个模拟上下文。该方法受到保护,但您可以通过反射或扩展FacesContext 来访问它。有一个使用 Mockito here 的示例实现。

    【讨论】:

      【解决方案2】:

      这个网址提供了一篇非常好的文章: 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();
          }
        }
      

      【讨论】:

        【解决方案3】:

        您可以使用例如 PowerMock,它是一个允许您扩展模拟库(如 Mockito)并具有额外功能的框架。在这种情况下,它允许您模拟 FacesContext 的静态方法。

        如果您使用的是 Maven,请使用以下 link 检查所需的依赖项设置。

        使用这两个注释来注释您的 JUnit 测试类。第一个注释告诉 JUnit 使用 PowerMockRunner 运行测试。第二个注释告诉 PowerMock 准备模拟 FacesContext 类。

        @RunWith(PowerMockRunner.class)
        @PrepareForTest({ FacesContext.class })
        public class PageBeanTest {
        

        使用 PowerMock 模拟 FacesContext 并使用 Mockitoverify() 以检查是否使用预期参数调用了 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");
        }
        

        我创建了一个blog post,它更详细地解释了上述代码示例。

        【讨论】:

          【解决方案4】:

          我给你一个例子来模拟 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);
                  }
          
              }
          
          }
          

          【讨论】:

            【解决方案5】:

            这是使用 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 下面的回答改编/扩展的。)

            【讨论】:

              【解决方案6】:

              在我的例子中,我能够用纯 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;
                    }
              

              【讨论】:

              • 有趣。我可能需要仔细研究一下 Groovy。
              • 这种方法的唯一问题是我需要将我想要使用的所有方法添加到模拟对象中
              【解决方案7】:

              我相信最好的解决方案没有在这里介绍。来了

              @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>
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-10-13
                • 2016-02-21
                • 1970-01-01
                • 1970-01-01
                • 2018-09-04
                • 2013-04-27
                • 1970-01-01
                相关资源
                最近更新 更多