简体   繁体   中英

How to call pass parameter that indicates what method should be called on instance

In me tests, i have many thing like:

try{
  someInstance.someMethod(argument)
}catch(Exception e){
  // assert exception message
}

and i would like to create method for this, howerver how? How do i pass method that should be invoked on specific isntance?

i tried

private <String,R> void  runCommandWithExpectedError(Function<String,R> fnc, String argument, String errorMessage){
    try {
        fnc.apply(argument);
    } catch (Exception e) {
        Assert.assertThat(e.getMessage(), Matchers.equalTo(errorMessage));
    }
}

However it complains that non static method cannot be refferenced from a static context. when calling it like

runCommandWithExpectedError(ChannelSftp::rm,"path","No such file or directory");

Also there is no way the method knows what instance it should invoke the method on, is something like that possible?

Thanks!

General statement

I wouldn't do this to simplify my unit tests. Unit tests are supposed to test each component of the code and should be easy to read and modify. Keeping them like that, even if they sound repetitive, it is how your code works and I would prefer to have a simple test which looks like all the other but shows easily what it does.

Visitor pattern

If you really want to go that way, this is something that can be done using the Visitor Design Pattern .

Imagine you have two classes like this:

public class Class1 {
    public int method1(String string) {
        return string.contains("something") ? 2 : 3;
    }
}

public class Class2 {
    public double method2(String string) {
        return string.contains("something") ? 3.0 : 4.0;
    }
}

Your tests look something like this:

@Test
public void test1() {
    Class1 class1 = new Class1();
    int result = class1.method1("something");
    //...assertions
}

@Test
public void test2() {
    Class2 class2 = new Class2();
    double result = class2.method2("else");
    //...assertions
}

If my understanding is correct, you rather want a single method testing the call to the method and eventually catching the exception to test its message.


First, you create a Visitor interface for each type you want to test:

public interface Visitor {
    int visit(Class1 class1, String argument);
    double visit(Class2 class2, String argument);
}

Then, you create an interface Visitable that accepts a Visitor type:

public interface Visitable {
    public void accept(Visitor visitor, String argument);
}

Once you did this, you should make the classes you want to test implementing the Visitable interface (basically making them "visitable" by a "visitor"):

public class Class1 implements Visitable {
    public int method1(String string) {
        return string.contains("something") ? 2 : 3;
    }

    @Override
    public void accept(Visitor visitor, String argument) {
        visitor.visit(this, argument);
    }
}

public class Class2 implements Visitable {
    public double method2(String string) {
        return string.contains("something") ? 3.0 : 4.0;
    }

    @Override
    public void accept(Visitor visitor, String argument) {
        visitor.visit(this, argument);
    }
} 

Now, you can create a TestVisitor class which implements the Visitor interface and performs the action you wish:

public class TestVisitor implements Visitor {

    @Override
    public int visit(Class1 class1, String argument) {
        return class1.method1(argument);
    }

    @Override
    public double visit(Class2 class2, String argument) {
        return class2.method2(argument);
    }
}

So, it is possible to refactor your unit tests with a single test performing the operation:

private final Visitor TEST_VISITOR = new TestVisitor();

private void sharedTestMethod(Visitable instance, String argument) {
    instance.accept(TEST_VISITOR, argument);
}

And your tests simply create the instance then call the sharedTestMethod :

@Test
public void test1() {
    Class1 class1 = new Class1();
    sharedTestMethod(class1, "something");
}

@Test
public void test2() {
    Class2 class2 = new Class2();
    sharedTestMethod(class2, "else");
}

Let's not forget the downsides (quoted by the link above):

Watch Out for the Downsides The arguments and return types for the visiting methods needs to be known in advance, so the Visitor pattern is not good for situtations where these visited classes are subject to change. Every time a new type of Element is added, every Visitor derived class must be amended. Also, it can be difficult to refactor the Visitor pattern into code that wasn't already designed with the pattern in mind. And, when you do add your Visitor code, it can look obscure. The Visitor is powerful, but you should make sure to use it only when necessary.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM