【问题标题】:Unable to MockUp a generic interface in JMockit无法在 JMockit 中模拟通用接口
【发布时间】:2016-09-30 23:45:49
【问题描述】:

我想模拟一个通用接口:

public interface IModel<T, S> {
    public S classify(T entity);
}

该接口由 3 个具体类子类化:TextModelImageModelScoringModel。这些具体类中的每一个都有不同的 T 和 S 参数。

我编写了一个通用方法,它接收具体模型类作为参数并生成模型的模拟版本:

private <T extends IModel<?, ?>> T mockModel(Class<T> modelClass) {
    return new MockUp<T>() {
        @Mock public Object classify(Object entity) { return null; }
    }.getMockInstance();
}

我知道IModel::classify 的输入和输出都有泛型类型,但我还没有找到在模型中使用实际泛型方法的方法。

调用此方法时,我得到一个IllegalArgumentException

java.lang.IllegalArgumentException:com.classificationmanager.model.$Impl_IModel 类型的值与 com.classificationmanager.model.TextModelFactory#createModel(com.classificationmanager.model.ModelDescriptor) 的返回类型 com.classificationmanager.model.TextModel 不兼容 在 com.classificationmanager.model.ModelFetcherTest$5.(ModelFetcherTest.java:110) 在 com.classificationmanager.model.ModelFetcherTest.mockAllFactories(ModelFetcherTest.java:109) ....... (省去你剩下的)

我认为获取和返回 Object 而不是 T 和 S 是问题所在,但在删除模拟方法并仅模拟类时,我得到了同样的异常:

private <T extends IModel<?, ?>> T mockModel(Class<T> modelClass) {
    return new MockUp<T>() {
    }.getMockInstance();
}

我可以做一个 switch-case 并返回一个具体的类,但这会很讨厌。

任何涉及 Expectations API 的解决方法也适用于我。

10 倍

【问题讨论】:

  • 为什么不使用@Mocked TextModel
  • 我可以,但是我必须模拟 ImageModel、ScoringModel 和 IModel 的所有未来具体子类
  • 你不是已经通过调用mockModel(TextModel.class) 来做到这一点,正如问题中所暗示的那样?
  • @Rogério 也许我不够清楚。我需要一个根据输入参数 Class 返回 IModel 的具体子类的泛型方法扩展 IModel>。声明一个特定的 Mocked TextModel、Mocked ImageModel 等...不会实现这一点,因为它需要对 IModel 的每个当前和未来子类进行特殊处理(声明)。

标签: java unit-testing generics junit jmockit


【解决方案1】:

也许以下示例可以提供帮助(尽管我仍然不明白这个问题 - 可能是 XY 问题的情况)。

public final class ExampleTest {
    public interface IModel<T, S> { S classify(T entity); }

    static class TextModel implements IModel<Integer, String> {
        @Override public String classify(Integer entity) { return "test"; }
    }

    static class ImageModel implements IModel<String, Image>  {
        @Override public Image  classify(String entity)  { return null; }
    }

    @Test
    public void createNonMockedInstanceForAnyModelClass() {
        IModel<Integer, String> m1 = mockModel(TextModel.class);
        String s = m1.classify(123);

        IModel<String, Image> m2 = mockModel(ImageModel.class);
        Image img = m2.classify("test");

        assertEquals("test", s);
        assertNull(img);
    }

    <T extends IModel<?, ?>> T mockModel(Class<T> modelClass) {
        // Or use newUninitializedInstance in case the model class doesn't
        // have a no-args constructor.
        return Deencapsulation.newInstance(modelClass);
    }

    @Test
    public void mockAllModelImplementationClassesAndInstances(
        @Capturing IModel<?, ?> anyModel
    ) {
        IModel<Integer, String> m = new TextModel();
        String s = m.classify(123);

        assertNull(s);
    }
}

【讨论】:

  • 10x 的答案,Deencapsulation::newInstance 方法是解决我的问题的好方法。在第二次测试中,Capturing 注释的字段究竟是做什么的?我在测试中没有看到anyModel 输入参数的任何用法。
  • @Capturing@Mocked 的效果扩展到所有实现/扩展模拟字段/参数(及其实例)的声明类型的类。
  • 是的,我知道,但是在您提供的测试中,TextModel 实例似乎根本没有被模拟,它只是调用具体实现并验证它实际上返回 null。我错过了什么?
  • 如果TextModel 没有被嘲笑,那么m.classify(123) 将返回"test",而不是null
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-24
  • 2012-08-17
相关资源
最近更新 更多