JayEsEm
JayEsEm

Reputation: 33

How do I reuse JUnit test methods for different testing contexts?

Up to this point, I have been developing tests for the support of a certain application in my project. Now that I have a set of tests up and running, I need to provide support for another similar application. The result for both applications should be the same in my system, as they both produce the same actions but from different contexts. The thing is, I am looking for a way with which I could reuse the tests that I have made for the first one, without copying their code for the other test case.

I am currently using JUnit 4 under Java EE 8.

My situation goes something like this. Given a test like the one that follows:

class TestExample {
    Context context;

    @Before
    public void setUp() {
        context = new ContextA();
    }

    @After
    public void terminate() {
        context.dispose();
    }

    @Test
    public void test1() {
        context....
    }
}

abstract class Context {
    @Override
    public void dispose() {
        ...
    }
}

class ContextA extends Context {
}

class ContextB extends Context {
}

I would like to be able to do the test once for ContextA and then for ContextB.

---o---

So after coming back at this problem after a while, I have gathered from this thread three possible solutions to my problem:

  1. Parametrization
  2. Test inheritance
  3. Context composition

However, after trying them all out on real test cases I have found none of them to be perfect solutions. They do solve the problem that I have presented on this question, but the moment a real case comes in and the contexts start differing slightly between them then these options start becoming complex and ugly.

In the end, I have opted for the simplest solution: copying the tests source code for each different context. It works, but the problem with this is that maintaining the tests gets harder and harder with time as a change to a single test must be replicated to all the copies of it.

Upvotes: 3

Views: 1675

Answers (2)

Timothy Truckle
Timothy Truckle

Reputation: 15622

A different approach could be to improve your design:

In your case both projects us the same base class. You could apply the Favor Composition over inharitance approach by turning Context into an normal class (removing the abstract keyword) and moving it tho a common base project. This way you have a single point where you test the behavior of Context.

Your specialized classes ContextA and ContextB would get an instance of Context injected as constructor parameter so that you can mock it in their own tests.

interface Context {
   public void dispose();
}

class ContextImpl implements Context {
    @Override
    public void dispose() {
        ...
    }
}

class ContextA implements Context {
    private final Context common;
    ContextA(@Inject Context common){
       this.common = common;
     @Override
    public void dispose() {
        common.dispose();
    }
}

class ContextB implements Context {
    private final Context common;
    ContextB(@Inject Context common){
       this.common = common;
     @Override
    public void dispose() {
        common.dispose();
    }
}

Then you verify (along with the special behavior of ContextA and ContextB respectively) that the expected methods on the passed instance of Context are called.

Yes you still have duplicated test, but they are so simple that they will never change as long as the method signatures in Context not change.


sometimes it might be even better to do it the other way around and inject the specialized implementations into the the class holding the common behavior...

Upvotes: 0

GhostCat
GhostCat

Reputation: 140427

How do I reuse JUnit test methods for different testing contexts?

Typically, you do not.

One core point of unit tests is: they help you to quickly identify bugs when you made changes to your production code later on. Meaning: a primary requirement for unit tests is that you are able to read and understand them quickly. Anything that makes that harder, like "complex" solutions to avoid code duplication is problematic.

From that point of view, the "rules" for test code are sometimes not the same as for production code. In production code, you want to avoid code duplication at (almost) all cost.

Yet, for test code, it can be perfectly fine to have two different test classes that do very similar things ... two times. Maybe, just maybe you extract common code into helper methods (like doing a specific, complicated assert) into some utility classes.

Having said that, one potential solution: if you really want to run the exact same tests on multiple "inputs", then I suggest you look at parameterized tests for example. Instead of hard-coding your context instance within @Before code, you could simply have multiple different context objects, and run all tests for each of those.

Upvotes: 2

Related Questions