0

I apologize if this has been answered before but either i don't know the correct verbiage or my google fu is bad.

I have a TestModel class which has the getters and setters for all the tests I use. Then I have a AdditionalTestModel class that extends the TestModel with additional getters and setters for that specific type of tests.

Now I have BuildTest Class that i want to be able to pass TestModel and any extended classes of TestModel.

public static Class<?> buildTest(Class<?> test, Class<?> template)
    throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    Class<?> testClass = test.getClass();
    Method[] testMethods = testClass.getMethods();
    for (Method method : testMethods) {
        String name = method.getName();
        if (name.startsWith("get")) {
            String testMethodType = method.getReturnType().getTypeName();
           // additional code removed//
        }
    } 

If instead of Class<?> i was using TestModel it would work for any test that i pass of Class type TestModel. But i want to be able to pass the extended class to this method as well without having to write a method for each extended class. Any recommendations? Adding information on the models in case it matters.

public class TestModel {


private String testDescription;
private String testName;
private String apiPath;
private String method;
private String expectedTest;
private Map<String, String> header = new HashMap<>();
private Object body;
private String expectedResult;
private String testCaseId;
private String testUUID;
private List testTypes;


public String getTestDescription() {
    return testDescription;
}

public void setTestDescription(String testDescription) {
    this.testDescription = testDescription;
}

public String getTestName() {
    return testName;
}

public void setTestName(String testName) {
    this.testName = testName;
}

public String getAPIPath() {
    return apiPath;
}

public void setAPIPath(String apiPath) {
    this.apiPath = apiPath;
}

public String getExpectedTest() {
    return expectedTest;
}

public void setExpectedTest(String testName) {
    this.expectedTest = testName;
}

public String getMethod() {
    return method;
}

public void setMethod(String method) {
    this.method = method;
}

public Map<String, String> getHeader() {
    return header;
}

public void setHeader(Map<String, String> header) {
    this.header = header;
}

public Object getBody() {
    return body;
}

public void setBody(Object body) {
    this.body = body;
}

public String getExpectedResult() {
    return expectedResult;
}

public void setExpectedResult(String expectedResult) {
    this.expectedResult = expectedResult;
}

public String getTestCaseId() {
    return testCaseId;
}

public void setTestCaseId(String testCaseId) {
    this.testCaseId = testCaseId;
}

public String getTestUUID() {
    return testUUID;
}

public void setTestUUID(String testUUID) {
    this.testUUID = testUUID;
}

public List getTestTypes() {
    return testTypes;
}

public void setTestTypes(List testTypes) {
    this.testTypes = testTypes;
}

}

public class AdditionalTestModel extends TestModel {

@Override public Object getBody() {

    return super.getBody();
}

}

Edit: per a request adding the call information here:

   @Test(dataProvider = "Default", threadPoolSize = THREADS, timeOut = API_TIME_OUT)
        @Description("")
        public void sampleTest(AdditionalTestModel testFromDataProvider) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        testSetup(testFromDataProvider);
        AdditionalTestModel test = BuildTest.buildTest(testFromDataProvider, template);
        Response response = RestAPI.call(test, testEnvironment);
        if (null != response) {
            ValidateAPIResponse.validateTestModel(test, response);
        } else {
            Assert.fail("Response is null, probably a bad method.");
        }
    }

Where testFromDataProvider is passed from a TestNg data provider.

Now LppEdd below already pointed out i could only assign the base class using generics so working on trying it his way, just have not gotten a chance to change things up yet.

Edit: Also realize now my question was bad. Thanks LppEdd. I should have asked How can I get a method to accept an instance of a class and an instance of any extended class

Grogimer
  • 1
  • 2

3 Answers3

0

You are close, you just need to use the extends modifier.

If the class passed in as the test and template parameter should be the same exact class type, you can do:

public static <T extends TestModel> Class<T> buildTest(Class<T> test, Class<T> template) { ... }

Otherwise you can do

public static Class<? extends extends TestModel> buildTest(Class<? extends TestModel> test, Class<? extends String> extends TestModel) { ... }

Which will allow different types to be returned and passed in to each parameter.

You can read up on Java generics and wilcards starting here: https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html

aarbor
  • 1,398
  • 1
  • 9
  • 24
  • Thanks for the quick response, will take a look at the document. However changing to: ' public static Class extends TestModel> buildTest(Class extends TestModel> test, Class extends TestModel> template) ' and still getting incompatible types AdditionalTestModel cannot be convert to java.langClass extends TestModel> – Grogimer Mar 14 '19 at 19:11
  • Changed it to ' public static Class buildTest(Class test, Class template) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { ' and still get an error cannot be applied to given types: Required java.lang.Class Found: AdditonalTestModel Reason cannot infer type-variable(s) T Argument mismatch; AdditionalTestModel cannot be converted to java.lang.Class – Grogimer Mar 14 '19 at 20:01
  • @Grogimer this cannot work as you're passing in an instance of TestModel. Not a Class. – LppEdd Mar 14 '19 at 20:04
  • @Grogimer can you add code to show how you are calling the method? – aarbor Mar 14 '19 at 21:19
0

Your buildTest method must accept a TestModel class.
You might be looking for something like

public static TestModel buildTest(
        final TestModel test,
        final TestModel template) {
    final Class<? extends TestModel> testClass = test.getClass();
    final Method[] testMethods = testClass.getMethods();

    for (final Method method : testMethods) {
        final String name = method.getName();
        if (name.startsWith("get")) {
            final String testMethodType = method.getReturnType().getTypeName();
            // additional code removed
        }
    }

    // Maybe
    return yourNewInstance; // yourNewInstance is a TestModel, or any class extending it
}

The template argument seems unused here (clarify).
What's the wanted return type? (clarify)

Usage example

final TestModel value1 = buildTest(new TestModel(), ...);
final TestModel value2 = buildTest(new AdditionalTestModel(), ...);
LppEdd
  • 20,274
  • 11
  • 84
  • 139
  • I striped a lot of code out where template is being used, template and test will always be objects of the same class type and returns an object of the same class type. Basically, I filling in missing data in the test object from the template object and then calling a data generator class to fill in data that needs to be randomized which was beyond the scope of the question so figured it would not be needed. – Grogimer Mar 14 '19 at 19:43
  • That was the original way i had it, and only works if i pass a TestModel object only. Trying to get it to take AdditionalTestModel object which is an extended class of TestModel. – Grogimer Mar 14 '19 at 19:55
  • @Grogimer I literally just tested it. It accepts an extending class (e.g. AdditionalTestModel). I never post code without testing. – LppEdd Mar 14 '19 at 19:56
  • @Grogimer Saw your updated answer. I'll repeat that this code works perfectly. – LppEdd Mar 14 '19 at 20:10
  • usage example for me was 'AdditionalTestModel test = BuildTest.buildTest(testFromDataProvider, template)' where testFromDataProvider is from a iterator of AdditionalTestModel. Will switch it all back to TestModel and give it another shot. – Grogimer Mar 14 '19 at 20:42
  • @Grogimer You cannot assign the result to an AdditionalTestModel. You can assign only to a base class, which is TestModel, using generics. – LppEdd Mar 14 '19 at 20:44
  • thanks, really appreciate the help. Will rework some of my code and update the thread tomorrow. – Grogimer Mar 14 '19 at 22:42
0

This looks to be exactly the same problem as must be solved by test frameworks. For example, see junit (https://junit.org/junit5/).

The core problem is how to obtain the collection of test methods of a class.

A direct solution would be to have the test class be required to answer its test methods, say, Collection<Function<Void, Void>> getTests(); This has several problems, one being that sub-classes must explicitly list their test methods, two being that sub-classes must be careful to add in the test methods from their super-class, and third, this really fits more as static behavior, which would try to shift java instance typing to the class layer, which just isn't supported by java.

An indirect solution would be to require that test methods satisfy a particular pattern (for example, must start with "test" and have no parameters), and use reflection to discover the methods. Or, use an annotation (say, @Test, which is what junit does) to mark out test methods, and again use the java reflection API to discover methods with the marker.

Thomas Bitonti
  • 1,179
  • 7
  • 14
  • Using TestNg, the problem is how we are using data providers, how compact we are trying to make the code and my lack of knowledge of java. I am used to writing api tests in node.js with the ava framework. Having the strict typing and having to build json objects is a shock to the system. :) – Grogimer Mar 14 '19 at 22:38