我有一个带有转发方法foo的类:

void foo( Concrete c, String s ) { c.bar( s ); }

我想测试foo是否确实向前。对我来说不幸的是,Concrete是第三方库中的类,并且是具体类型,而不是接口。因此,我必须在JMock中使用ClassImposteriser来模拟Concrete,因此在我的测试案例中,我这样做:
@Test
public final void testFoo() {
   Mockery context = new JUnit4Mockery() {{
      setImposteriser(ClassImposteriser.INSTANCE);
   }};

  final Concrete c = context.mock(Concrete.class);
  final String s = "xxx" ;

  // expectations
  context.checking(new Expectations() {{

     oneOf (c).bar(s); // exception gets thrown from here
  }});


  new ClassUnderTest.foo( c, s );
  context.assertIsSatisfied();

}

不幸的是,Concrete.bar依次调用了抛出的方法。该方法是最终的,因此我无法覆盖它。此外,即使我注释掉new ClassUnderTest.foo( c, s );行,当JMock设置异常时(而不是在调用foo时)也会引发异常。

那么,如何测试ClassUnderTest.foo方法确实可以转发给Concrete.bar呢?

编辑:
是的,bar是最终的。

我的解决方案(不是一般的解决方案)是使用第三方库中的“Tester”类来正确设置Concrete

最佳答案

从问题文本中还不清楚Concrete.bar()是否为final或Concrete.somethingElse()是否为final并从Concrete.bar()调用。

如果Concrete.bar()不是最终的,则为Concrete创建一个手写存根,如下所示:

public class ConcreteStub extends Concrete
{
    public int numCallsToBar = 0;
    @Override
    public void bar(String s) { numCallsToBar++; }
}

并在您的测试代码中:
ConcreteStub c = new ConcreteStub();
foo(c,"abc");
assertEquals(1,c.numCallsToBar);

如果Concrete.bar()是最终的,则它会更复杂,答案取决于Concrete的复杂性以及您的项目对Concrete类的使用。如果您对Concrete的使用非常简单,我会考虑将Concrete包装在接口(Adapter Pattern)中,然后可以更轻松地进行模拟。

适配器模式解决方案的好处:在项目使用Concrete之后,可以通过命名界面来澄清行为。更容易测试。

适配器模式解决方案的缺点:引入更多的类,而对生产代码的好处可能很小。我不知道Concrete会做什么,并且将Concrete包裹在接口中可能不切实际。

08-07 00:27