最近在编写单元测试的时候,遇到两个问题。 问题1:因为项目使用了JSF框架,很多方法使用了FacesContext这个类,导致在编写这些方法的相关单元测试时,遇到环境问题。 问题2:由于项目与第三方服务有交互(使用HTTP,第三方服务测试服务位于公网),而这两个项目存在相互request的需求,这意味着当第三方服务需要request本项目的时候,只能通过公网IP来访问。这就面临着无法在本地做"第三方服务请求该项目"的相关接口测试。
通过上网查了相关的资料,也找到了一些解决方案。在这里做一个总结。
环境:
JSF 2.0 JUnit 4 - Link Mockito 2.0 - Link moco - Link
简要介绍:
Mockito:Mockito是一个针对Java的mocking框架。大白话来解释一下它的功能:Mockito可以在你需要某个对象的时候,模拟出一个来使用它。或者,当你对某个对象调用某个方法的时候,你希望返回的东西,它也可以给你模拟出来。当然,Mockito的功能还不止这些。 moco:可以模拟出一个服务器,让你可以事先配置好请求uri和对于的响应,使得在进行与HTTP相关的单元测试的时候,不必再手动配置一个Server。你可以在单元测试开始时,启动它,在单元测试结束后,关闭它。
Mockito使用:
首先,假设项目中使用了:
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.getCurrentInstance()会返回null,所以需要mock一个FacesContext。FacesContext有一个方法:
protected void javax.faces.context.FacesContext.setCurrentInstance(FacesContext context)
所以,首先创建一个抽象类FacesContextMocker 继承FacesContext类:
package com.fenxiangz.core;import javax.faces.context.FacesContext;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;public abstract class FacesContextMocker extends FacesContext {
private FacesContextMocker() {
}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;
}
}
为什么是抽象类?因为FacesContext是一个抽象类,我们不希望实现FacesContext里面的方法,所以在这里也定义成抽象类。 这个抽象类有一个静态方法:mockFacesContext(),当我们在写单元测试的时候,就可以直接通过FacesContextMocker.mockFacesContext()来获取一个FacesContext。进而,通过Mockito配置FacesContext的实例来预先配置一些FacesContext使用的行为。 有了FacesContextMocker,我们就可以先写一个简单的单元测试代码了。如下:
package foo.test;import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;import java.util.HashMap;
import java.util.Map;import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;import org.junit.Test;
import foo.AlphaBean;
public class AlphaBeanTest {
@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(); }
}
}
这样,就使得即使没有JSF运行环境,也可以正常执行包含FacesContext的逻辑代码。
moco使用:
https://github.com/dreamhead/moco/blob/master/moco-doc/usage.md
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于