Inheritance (or an alternative) in Parameterized jUnit tests
Asked Answered
O

1

6

I'd like to do something like this in jUnit:

@Runwith(Parameterized.class)
public abstract class BaseTest {

     protected abstract List<Object[]> extraParams();
     protected abstract ClassUnderTest testObject;

     @Parameters
     public Collection<Object[]> data() {
         List<Object> params = ...; // a standard set of tests
         params.addAll(extraParams());
         return params;
     }

     @Test
     public doTest() {
         // assert things about testObject
     }
}

public class ConcreteTest extends BaseTest {
     protected ClassUnderTest = new ConcreteClass(...);
     protected List<Object[]) extraParams() {
         List<Object> extraParams = ...; // tests specific to this concrete type
         return extraParams;
     }
}

So that by extending this class, I run a bunch of standard tests against the object under test, plus some extra ones specified in the concrete class.

However, jUnit requires that the @Parameters method is static. How else can I tidily achieve the aim, of having a set of standard parameters plus extra ones in the concrete classes?

The best I've come up with so far is to have an un-annotated Collection<Object[]> standardParams() in the abstract class, and to require that the subclass contain a method:

 @Parameters
 public Collection<Object[]> data() {
     List<Object> params = standardParams();
     params.addAll(...); // extra params
     return params;
 }

... but this isn't as tidy as I'd like, as it puts too much responsibility on the writer of the subclass.

Outlaw answered 28/10, 2016 at 16:41 Comment(2)
Slim: Can you have a look at my answer ?Anemograph
@javaguy sorry it took a while -- it was the weekend.Outlaw
A
2

JUnit expects that the @Parameters method must be static and if you don't provide the static method, it throws No public static parameters method on class Exception.

But your requirement can be acheived by implementing org.junit.rules.TestRule as below:

BaseTest class

public abstract class BaseTest {

    @Rule
    public MyBaseTestRule myProjectTestRule = new MyBaseTestRule(data());

    protected abstract List<Object[]> extraParams();

    public List<Object[]> data() {  
        List<Object[]> listTotal = new ArrayList<>();
        listTotal.addAll(extraParams());
        //add your base test data here
        return listTotal;
    }

    public abstract List<Object[]> extraParams();
}

ConcreteTest class

public class ConcreteTest extends BaseTest  {

    @Override
    public List<Object[]> extraParams() {
        List<Object[]> list = ...//set up data here
        return list;
    }   

    @Test
    public void test1() {
        Object[] testData = myProjectTestRule.getTestData();
        //use the test data for the test
        //Example: Assume addition of two integers scenario and data 
        //data[0] expectedresult, data[1],[2] inputs
        //Assert.assertEquals((int)data[0], (int)(data[1]+data[2]));

    }

    //add other test cases
}

MyBaseTestRule class:

import java.util.List;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class MyBaseTestRule implements TestRule { 

    private final List<Object[]> totalTestData;

    private final int totalTestsSize;

    private int currentTestIndex;

    public MyProjectTestRule(List<Object[]> list) {
      this.totalTestsSize = list.size();
      this.totalTestData = list;
   }

   public Object[] getTestData(){
      return totalTestData.get(currentTestIndex);
   }

   @Override
   public Statement apply(Statement stmt, Description desc) {

      return new Statement() {

         @Override
         public void evaluate() throws Throwable {
            for(int i=0; i<totalTestsSize; i++) {
                currentTestIndex = i;
                stmt.evaluate();
            }
         }
       };
    }
}
Anemograph answered 29/10, 2016 at 8:19 Comment(3)
Thanks for this. If I understand it right, we're rolling our own implementation of a "run the same test repeatedly" framework, instead of using @RunWith(Parameterized.class). But can you explain how the @Test method knows which item from data() to use each time evaluate() is run?Outlaw
I'm giving it a week in case someone comes up with something that involves less re-invention. Don't be offended. I sometimes have to tell people to be less hasty accepting my answers, in case something better comes along.Outlaw
Is it possible to modify my test-names? If I have a test-class with 10 tests, and extraParams returns 5 parameters, 50 tests are run, but the IDE only displays 10 run tests, since they all have the same name. is it possible to change the names of the test depending on the parameters? This answer is wrong imo.Whiles

© 2022 - 2024 — McMap. All rights reserved.