使用 FactoryBean结合Jmock实现动态Mock类的注入

Author:Willam2004
[b]引言:[/b]
FactoryBean:我们在使用Spring过程中一般都是使用基本的的配置,在Spring配置中,还有一种特殊的FactoryBean,这种bean,可以动态的帮我们创建我们需要的bean,如: ProxyFactoryBean,通用的用于获得AOP代理的工厂bean。可以方便帮我们配置AOP的拦截类.
factorybean关键的是接口 org.springframework.beans.factory.FactoryBean,它有两个重要的方法:

Object getObject() throws Exception;
Class getObjectType();

getObject是要实际返回的bean对象
getObjectType是返回Bean对象对应的ObjectType.
原理可以参考:http://dengyin2000.iteye.com/blog/47443
JMock:http://www.jmock.org/
JMock是我们单元测试中经常用到的一个Mock框架.
[b]问题:
[/b]单元测试的一个重要难点就是外部接口的依赖,为了保证单元测试的持续有效,我们对外部接口的都要进行Mock.原始的mock,是在单元测试中,直接将接口手动实现一个mock类,在单元测试中再进行注入.但这种缺点是[color=red],如果接口有变更,如新增方法(比较频繁)[/color],原有的实现类就需要重新编写.对于依赖二方库,因为更新的延迟,还会导致单元测试报错,需要重新做版本进行发布,耗时耗力.使用jmock就可以避免这个问题,因为你不需要手动再实现这个类,只需要对你关注的方法进行断言就可以了.如代码:
我们需要测试类:
public class RealService {
private MorganService morganService;
/**
* @param morganService the morganService to set
*/
public void setMorganService(MorganService morganService) {
this.morganService = morganService;
}
public void doExecute(String memberId) {
String s = morganService.findMemberById(memberId);
System.out.println(s);
}
}

其中MorganService服务就是我们依赖的接口类,采用jmock的测试如:

public class RealServiceTest extends TestCase {
private RealService service;
/**
* Test method for {@link com.alibaba.RealService#doExecute(java.lang.String)}.
*/
public void testDoExecute() {
Mockery context = new Mockery();
//Mockery是jmock的context,它负责mock对象的创建和mock检查.
final MorganService morganService = context.mock(MorganService.class);
context.checking(new Expectations() {
{
//一次调用morganService的findMemberById方法,返回值为字符串good
oneOf(morganService).findMemberById("test120");
will(returnValue("good"));
}
});
service.setMorganService(morganService);
service.doExecute("test120");
context.assertIsSatisfied(); //检查mock对象有没有正确调用.
}
}

但是目前我们的单元测试,一般是扩展Spring的单元测试进行Bean自动注入,不需要我们手动进行set方法注入,而上面Jmock的使用,需要我们将测试servcie手动调用set方法才能将Mock对象进行注入.
[b]解决方案:
[/b] 关键问题:我们不能通过配置的方式将mock对象注入到测试的服务类.FactoryBean可以帮我们动态生成我们需要的Bean进行注入.而不用关心到底是什么样的类型.
步骤一:新建MockFactoryBean


public class MockFactoryBean implements FactoryBean {
private String interfaceName;
private boolean expectation = true;
private IExpectaion iexpectation;
/**
* @return the iexpectation
*/
public IExpectaion getIexpectation() {
return iexpectation;
}
/**
* @param iexpectation the iexpectation to set
*/
public void setIexpectation(IExpectaion iexpectation) {
this.iexpectation = iexpectation;
}
/**
* @return the expectation
*/
public boolean isExpectation() {
return expectation;
}
/**
* @param expectation the expectation to set
*/
public void setExpectation(boolean expectation) {
this.expectation = expectation;
}
/**
* @return the interfaceName
*/
public String getInterfaceName() {
return interfaceName;
}
/**
* @param interfaceName the interfaceName to set
*/
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
@SuppressWarnings("unchecked")
@Override
public Object getObject() throws Exception {
Mockery context = new Mockery();
final Object o = context.mock(getObjectType());
if (this.isExpectation()) {
context.checking(new Expectations() {
{
//因为Expectations是动态的,所以我将此方法抽出来接口,方便以后的扩展
iexpectation.expectaion(o, this);
}
});
}
return o;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
@SuppressWarnings("unchecked")
@Override
public Class getObjectType() {
// TODO Auto-generated method stub
try {
return Class.forName(interfaceName);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
throw new BeanInstantiationException(this.getClass(), "Can't find the class for use the "
+ getInterfaceName());
}
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
@Override
public boolean isSingleton() {
return true;
}
}

上面的IExpectaion是一个定义的接口,主要是用来方便自定义断言,它采用泛型的方式进行处理:

public interface IExpectaion {
/**
* @param o
* @param mockFactoryBean
*/
void expectaion(T o, Expectations expectations);
}

这样我们看下我们的applicationContext的配置:






com.alibaba.MorganService





开发人员针对每个外部接口,只需要再实现一个IExpectaion的接口类,进行相应断言就可以进行开发,而不需要再实现全部的接口方法,也避免了接口的方法变更的导致的单元测试错误.
[b]上述方案的改进点:[/b]
1.MockFactory其实获取Object的class,可以不需要通过直接的InterfaceName来填写,直接通过iexpectation属性中的泛型参数进行获得.
[b]其他方案[/b]
除了以上的方案,还可以通过JTester框架的Mocked注解方式进行处理.这种方式更方便,如:

@SpringApplicationContext("applicationContext.xml")
public class RealServiceJTester extends JTester {
@Mocked
@MockedBean
private MorganService morganService;
@SpringBeanByName
private RealService realService;
@Test
public void testRealService() {
new Expectations() {
{
// some expectation
}
};
realService.doExecute("test100");
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FactoryBean是Spring框架中的一个接口,它提供了一种在容器中创建bean的方式。使用FactoryBean可以实现延迟加载,即在需要的时候才创建bean实例。下面是一个简单的示例: 1. 创建一个工厂类,实现FactoryBean接口: ``` public class MyBeanFactory implements FactoryBean<MyBean> { private boolean lazyInit = true; @Override public MyBean getObject() throws Exception { System.out.println("getObject() called"); return new MyBean(); } @Override public Class<?> getObjectType() { return MyBean.class; } @Override public boolean isSingleton() { return true; } public boolean isLazyInit() { return lazyInit; } public void setLazyInit(boolean lazyInit) { this.lazyInit = lazyInit; } } ``` 2. 配置bean: ``` <bean id="myBeanFactory" class="com.example.MyBeanFactory"> <property name="lazyInit" value="true"/> </bean> ``` 3. 在需要使用MyBean的地方,通过ApplicationContext获取实例: ``` ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); MyBean myBean = (MyBean) context.getBean("myBeanFactory"); ``` 在这个例子中,当ApplicationContext获取MyBean实例时,如果lazyInit属性为true,则不会立即创建MyBean实例,而是在第一次调用getObject()方法时才会创建。如果lazyInit属性为false,则在容器启动时就会创建MyBean实例。 使用FactoryBean可以实现延迟加载,避免在启动时就创建所有的bean,提高了应用程序的启动速度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值