0

I have been setting up some contract JUnit 4 tests (following advice in answer to this question) - e.g. if I have an interface (I've included generics in the example, since I'm using them in my project) - but, this is a significantly simplified example:

public interface MyInterface<T> {
  public T myMethod();
}

I can create an abstract contract test - e.g.:

public abstract class AbstractMyInterfaceTest<T> {

  private final MyInterface<T> _impl;  
  // plus, store stuff to test against;

  public AbstractMyInterfaceTest(MyInterface<T> impl, ... stuff to test against){
    _impl = impl;
    // stuff to test against...
  } 

  @Test
  public void testMyMethod(){
    // test some stuff...
  }
}

...and extend this abstract test class for each implementation (which I am running with @RunWith(Parameterized.class)). I'm also passing some variables to test the implementation of the interface against into the abstract test class.

However, in actuality, some interfaces return objects that are implementations of other interfaces, and I would like to be able to test this implementation using the abstract contract class for that interface (...and against the relevant variables). For example, a method in MyInterface might return an Iterator - which is an interface. I have a separate AbstractIteratorTest, which runs a contract test for Iterator against a set of values, and I want to check that the Iterator returned by the implementations of MyInterface passes the contract tests in AbstractIteratorTest with the expected set of values.

At the moment, I'm adding the following to the abstract test classes:

// N.b. not setting @Test - so doesn't run all tests twice...
public void runTests(){
  testMyMethod();
  // etc.
}

However, this doesn't run each test independently (e.g. running setUp() before each test method), so there are problems if those contract tests expect the implementation to be in a particular state for each test. (A specific example would be testing implementations of the Iterator interface, where each test method can change the state of the tested Iterator)

Is there was a better way of doing this, perhaps making use of features in JUnit 4?

I've seen stuff about the @RunWith(Suite.class) annotation, but that seems to be about running multiple test classes from a single test class, and doesn't allow you to pass variables into the different test classes that are being run. There may be a way to use this Suite to solve my problem, but I'm not sure what it is...

Community
  • 1
  • 1
amaidment
  • 6,942
  • 5
  • 52
  • 88
  • You might like to explain the "some interfaces return objects that are implementations of other interfaces, and I would like to be able to test this implementation using the abstract contract class for that interface" bit more precisely. I think i understand that, but i could have it wrong. – Tom Anderson Jul 06 '12 at 08:19
  • Does a run of all the tests in `AbstractMyInterfaceTest` kick off a single run of `AbstractAnotherInterfaceTest`, or multiple? Is `MyInterface` really as simple as you indicate, just a factory for another object? – Tom Anderson Jul 06 '12 at 08:19

1 Answers1

0

Firstly, if you're subclassing your abstract test class, you don't need to use Parameterized. You could use Parameterized instead of subclassing, but you're unlikely to need both. Unless you want to test multiple instances of each implementation, which i will demonstrate below!

One way to do this would be to make the tests for returned instances simply be more subclasses or parameterisations of AbstractAnotherInterfaceTest.

Say you have:

public abstract class AbstractAnotherInterfaceTest {

    private AnotherInterface instance;

    protected AbstractAnotherInterfaceTest(AnotherInterface instance) {
        this.instance = instance;
    }

    @Test
    public void everythingIsOkay() {...}

}

And you had:

public class Pony implements AnotherInterface { ... }
public class PonyProducingMyInterface implements MyInterface<Pony> { ... }

You could write:

@RunWith(Parameterized.class)
public class PonyTest extends AbstractAnotherInterfaceTest {

    @Parameterized.Parameters
    public static Collection<Pony> ponies() {
        return Arrays.asList(
            new Pony(), // base case
            new PonyProducingMyInterface().myMethod() // testing the factory
        );
    }

    public PonyTest(Pony instance) {
        super(pony);
    }

}

This is admittedly a bit weird, though, because the new PonyProducingMyInterface().myMethod() case is really a test for PonyProducingMyInterface, but is classified under AbstractAnotherInterfaceTest.

The only approach i can think of that would make use of the AbstractAnotherInterfaceTest tests in some subclass of AbstractMyInterfaceTest would be to write a custom ParentRunner which knew how to set up instances of AbstractAnotherInterfaceTest as children of instances of AbstractMyInterfaceTest. You'd probably want to make that a bit generic, using annotations to guide the process.

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • Re. your first point, I am running my sub-tests with Parameterized, because I want to test multiple instances of each implementation - that was the point of my previous question referenced at the top. – amaidment Jul 06 '12 at 08:33
  • Aha. Sorry, i read the previous question, but not carefully enough. – Tom Anderson Jul 06 '12 at 08:34
  • Thanks for the help. Your solution of using the `MyInterfaceImpl.getOtherInterface()` as a parameter in the `OtherInterfaceTest` is, as you say, a bit weird - it also doesn't work in my project because there's no guarantee of which implementation of `OtherInterface` will be returned by `MyInterfaceImpl`. Your other solution - a custom `ParentRunner` - looks like overkill. – amaidment Jul 06 '12 at 09:11
  • It may look like overkill, but i believe it is the only way to run the tests you want as individual JUnit tests. Ultimately, it is the runners which enumerate the set of tests (as well as run them - JUnit doesn't separate those concerns well), so if you want to add tests of a kind JUnit doesn't know about natively, then you have to write a new runner. – Tom Anderson Jul 06 '12 at 10:02