如何为接口默认方法编写Junit测试

26
请帮忙编写针对接口默认方法的Junit测试代码。
public interface ABC<T, D, K, V> {
    default List<String> getSrc(DEF def, XYZ xyz) throws Exception {
    }
}

ABC: 接口名称。 DEF 和 XYZ: 类名。


可以有人给我建议吗,在这个问题上。 - Jech
1
可能是[在接口(Java 8)中的默认方法应该进行单元测试吗?]的重复问题(https://dev59.com/iV8e5IYBdhLWcg3wVJEC) - Arpit Aggarwal
@Arpit,我检查了一下...我没有得到正确的答案。你能否为我的例子解释一下? - Jech
5个回答

33

如果你正在使用Mockito,测试默认(也称为“defender”)方法的最简单方法是使用接口类字面值2创建一个spy1。然后可以像正常情况下一样在返回的 spy 实例上调用默认方法。以下示例演示了这一点:

import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;

interface OddInterface {
    // does not need any unit tests because there is no default implementation
    boolean doSomethingOdd(int i);

    // this should have unit tests because it has a default implementation
    default boolean isOdd(int i) {
        return i % 2 == 1;
    }
}

public class OddInterfaceTest {
    OddInterface cut = spy(OddInterface.class);

    @Test
    public void two_IS_NOT_odd() {
        assertFalse(cut.isOdd(2));
    }

    @Test
    public void three_IS_odd() {
        assertTrue(cut.isOdd(3));
    }
}

(使用Java 8和mockito-2.24.5进行测试)

1人们经常警告使用spy可能是代码或测试问题的迹象,但测试默认方法是使用spy的一个完美例子,这是一个的想法。

2截至本篇文章撰写时(2019年),接受类字面值的spy签名被标记为@Incubating,但自2014年以来已经存在于mockito-1.10.12中。此外,Mockito中默认方法的支持mockito-2.1.0发布于2016年以来一直存在。看起来这个方法在未来的Mockito版本中仍然会继续工作。


7

如答案所建议的那样,创建接口的实现类并进行测试,例如我修改了您的ABC接口中的getSrc方法,如下所示:

import java.util.ArrayList;
import java.util.List;

public interface ABC<T, D, K, V> {

    default List<String> getSrc(DEF def, XYZ xyz) {
        final List<String> defaultList = new ArrayList<>();
        defaultList.add("default");
        defaultList.add("another-default");
        return defaultList;
    }
}

创建了一个实现类来实现相同的功能,如果需要,您可以创建另一个方法调用超类方法并为两个方法都写上@Test注解,就像我所做的一样:

import java.util.List;

public class ABCImpl implements ABC<String, Integer, String, Integer> {

    public List<String> getSrcImpl(DEF def, XYZ xyz) {
        final List<String> list = getSrc(def, xyz);
        list.add("implementation");
        return list;
    }
}

对应实现的测试类如下所示:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import java.util.Collection;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

public class ABCImplTest {

    private ABCImpl abcImpl;

    @Before
    public void setup() {
        abcImpl = new ABCImpl();
    }

    @Test
    public void testGetSrc() throws Exception {
        List<String> result = abcImpl.getSrc(new DEF(), new XYZ());
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("default", "another-default"));
    }


    @Test
    public void testABCImplGetSrc() throws Exception {
        List<String> result = abcImpl.getSrcImpl(new DEF(), new XYZ());
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("default", "another-default", "implementation"));
    }
}

非常感谢。它已经起作用了。 - Jech
1
如果答案有效,请@Jech接受它- http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work - Arpit Aggarwal
为此打开一个单独的线程,不要将多个问题合并在一起。 - Arpit Aggarwal
2
我认为创建一个内部类或匿名类实现可能更好地测试默认方法,因为实现可能会在未来覆盖默认方法。 - David Barda
@DavidBarda是正确的,不要仅为测试目的创建实现,也不要通过实现本身来测试它。 - Joaquín L. Robles
显示剩余2条评论

4
我认为有一种更简单的方法。它包括在测试类中实现具有待测试方法的接口,并直接调用默认类型的方法。如果必要,被内部调用的对象将被模拟。前面的示例将如下所示:

接口)

public interface ABC<T, D, K, V> {
    default List<String> getSrc(DEF def, XYZ xyz) throws Exception {
      list<String>() result=new Arraylist<String>();
      result.add(def.toString());
      result.add(xyz.toString());
      return result;
    }
}

测试类

...        
@RunWith(MockitoJUnitRunner.class)
public class ABCTest implements ABC{

    @Test
    public void testGetSrc() {

        list<String>() result=getSrc(new DEF("Hi!"),new XYZ("bye!"));

        int actual=result.size();
        int expected=2;
        assertEquals(expected, actual);

    }

如果您需要测试异常的启动,只需通过错误的参数强制其释放并正确配置测试即可。
...        
@Test(expected = GenericRuntimeException.class)
public void test....
...

我已经使用类似的代码进行了检查,它可以正常工作。同时,在覆盖率分析中也被正确地收集。


4
答案非常简单。不需要嘲笑或监视,只需为接口创建一个匿名对象而不覆盖默认方法。
例如:
interface Adder {
  default sum(Integer...n) {
    return Arrays.stream(n).reduce(0, Integer::sum);
  }
} 

// Junit 4
class AdderTest {

  private Adder adder;

  @Before
  public void setup() {}
    adder = new Adder(){}; // not overriding default methods
  }

  @Test
  public void testSum() {
    Assert.assertEquals(3, adder.sum(1, 2));
  }
}

2
你可以创建一个实现你的接口的类,也可以让你的测试实现它。第二个选项似乎是一个更短的解决方案。
public class FunctionCallingTransactionTemplateTest implements FunctionCallingTransactionTemplate {
    private final Object object = Mockito.mock(Object.class);

    @Test
    public void should_invoke_function() throws Exception {
        // given
        when(object.toString()).thenReturn("salami");

        // when
        String result = functionCallingTransactionTemplate().execute((status) -> object.toString());

        // then
        assertThat(result).isEqualTo("salami");
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接