views:

989

answers:

4

I have class with a forwarding method foo:

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

I wish to test that foo in fact forwards. Unfortunately for me, Concrete is a class in a third-party library, and is a Concrete type, not an interface. Thus I must use ClassImposterizer in Jmock to mock Concrete, so in my testcase I do this:

@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();

}

Unfortunately, Concrete.bar in turn calls a method that throws. That method is final, so I can't override it. Further, even if I comment out the line new ClassUnderTest.foo( c, s );, the exception is thrown when JMock sets up exceptions, not when foo is called.

So how can I test that method ClassUnderTest.foo does forward to Concrete.bar?

On edit: yes, bar is final.

My solution, which is not a general one, was to use a "Tester" class in the third-party library to correctly set up Concrete.

+5  A: 

It's not clear from the question text if Concrete.bar() is final or if Concrete.somethingElse() is final and called from Concrete.bar().

If Concrete.bar() is not final, create a hand-written stub for Concrete like this:

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

and in your test code:

ConcreteStub c = new ConcreteStub();
foo(c,"abc");
assertEquals(1,c.numCallsToBar);

If Concrete.bar() is final, it is more complicated and the answer depends on the complexity of Concrete and your project's use of the Concrete class. If your use of Concrete is simple enough, I would consider wrapping Concrete in an interface (Adapter Pattern) that you can then mock out easier.

Benefits to the Adapter Pattern solution: Possibly clarify behavior by naming interface after your project's use of Concrete. Easier to test.

Drawbacks to the Adapter Pattern solution: Introduces more classes with possibly little benefit to production code. I don't know what Concrete does and it may not be practical to wrap Concrete in an interface.

Alex B
A: 

If a method is final then we can't do much about it. If this is a third-party library, then we would consider wrapping it in a veneer layer and mocking that, then doing integration tests to test against the library. There are other frameworks that will mock locked-down code, but we don't support it because we don't think it's a great idea.

Steve Freeman
+2  A: 

See http://www.jmock.org/mocking-classes.html for info about mocking classes and how to bypass final limitations.

Nat
A: 

Use a more capable mocking tool, such as JMockit. Your test could then be written as:

@Test
public void testFoo(final Concrete c)
{
  final String s = "xxx";

  new Expectations() {{
    c.bar(s);
  }};

  new ClassUnderTest().foo(c, s);
}

For JMockit, it makes no difference if Concrete is an interface, a final class, an abstract class, or whatever. Also, there is no need to use @RunWith, extend a base test class, or call any method like assertIsSatisfied(); it's all done automatically, in a transparent way.

Rogerio