我们可以在测试服务本身时模拟一个服务方法吗?

时间:2014-05-23 07:32:14

标签: java unit-testing junit mockito mybatis

我正在开发一个项目,我将MyBatis注释用作持久性框架。因此,我必须为'mapper'创建一个接口,并在服务中组合映射器,如:

class XYZServiceImpl{

    public XYZMapper getXYZMapper(){
        return SessionUtil.getSqlSession().getMapper(XYZMapper.class)
    }

}

现在,在使用Mockito对服务进行单元测试时,我正在尝试为mapper注入一个模拟器。但是因为我在一个XYZService实例中注入mock,如何模拟服务本身的方法,在这种情况下getXYZMapper()就是我想要存根的东西。虽然我有一个在服务中创建实例XYZMapper的解决方案,而不像上面的代码那样按需调用:

  Class XYZServiceImpl{

        XYZMapper mapper;

        public void useXYZMapper(){
            mapper = SessionUtil.getSqlSession().getMapper(XYZMapper.class);
        }

    }

但这会带来很多代码更改(当然我可以重构)但是有没有办法实现而无需进行代码更改?

在类中使用mapper实例的“纯粹主义”方法是,方法1在性能方面优于方法2吗?

编辑:这里XYZMapper是一个界面。类似的东西:

public interface XYZMapper{
    @Select("SELECT * FROM someclass WHERE id = #{id}")
    public SomeClass getSomeClass(int id);

}

编辑:我面临类似的情况,但有一个变化,我有一个服务,我想测试像XYZServiceImpl。现在它有一个方法getXYZDetails(),它在服务中处理了很多业务逻辑。现在,如果getXYZDetails如下所示:

public XYZDetails getXYZDetails(int id){

  XYZDetails details = new XYZDetails();

  details.set1Details(fetchSet1Details(id));

  //Perform some business logic

  details.set2Details(fetchSet2Details(id));

  if(details.set2Details() != null){
    for(int i = 0; i < details.set2Details().size(); i++){
      flushTheseDetails(i);
    }
  }
  .
  .

}

请注意fetchSet1Details(),fetchSet2Details(),flushTheseDetails分别是公共服务,公共和私人服务。

我想知道一种方法,可以在测试getXYZDetails()时模拟/存根这些方法,从而使我能够

2 个答案:

答案 0 :(得分:1)

您可以使用多种选项。

注入依赖项

当方法只返回对象的外部依赖关系时,这仅适用于getXYZMapper之类的简单方法。这可能需要创建新的XYZServiceImpl实例,例如,将mapper绑定到每个请求打开的连接。

在对象

中封装方法行为

获得类似结果的另一种方法是使用factoryservice locator 像这样:

public class XYZServiceImpl {
    public XYZServiceImpl(XYZMapperFactory mapperFactory) {
        this.mapperFactory = mapperFactory;
    }

    public XYZMapper getXYZMapper() {
        return mapperFactory.getMapper();
    }
}

这将允许您使用返回模拟映射器的实现轻松替换test中的factory。

类似的方法可以用于将其移动到其他类或类的其他方法fetchSet1DetailsfetchSet2DetailsflushTheseDetails。如果该方法包含复杂的(并且可能是松散相关的)逻辑,则将其移动到单独的类中是一个很好的候选者。想想这些方法的作用。通常,您可以将其中一些必要且不相关的部分移动到其他类或类中,这使得模拟它们变得更加容易。

子类

这不是recommended,但在遗留代码中,有时作为临时解决方案非常有用。

在您的测试子类中,您需要测试并覆盖所需的方法:

@Test
public void someTest() {
   XYZServiceImpl sut = new XYZServiceImpl() {
       public XYZMapper getXYZMapper() {
           return mapperMock;
       }
       public Whatever fetchSet1Details() {
           return whateverYouNeedInTest;
       }
   }

   sut.invokeMethodUnderTest();
}

您可能需要做的唯一事情是将私有方法的访问修饰符更改为package-private或protected,以便您可以覆盖它们。

的Alexa

此方法也在discouraged中,但您可以使用mockito间谍:

XYZServiceImpl realService = new XYZServiceImpl();
XYZServiceImpl spy = Mockito.spy(realService);

when(spy.fetchSet1Details()).thenReturn(whaeveryouneed);
when(spy.getXYZMapper()).thenReturn(mockMapper);

spy.methodUnderTest();

答案 1 :(得分:0)

我会建议&#34;纯粹主义者&#34;这样做的方法是在构造函数中接受XYZMapper实例并将其存储在本地字段中。

在生产用途中,您可以通过例如SQLXYZMapper,它将与您的数据库进行交互。在测试使用中,您可以传入一个可以验证与之交互的模拟对象。