【问题标题】:Mockito fails on NoSuchMethodError due to method being in abstract class using generic method由于方法在使用泛型方法的抽象类中,Mockito 在 NoSuchMethodError 上失败
【发布时间】:2016-06-10 14:57:26
【问题描述】:

我正在运行一些服务测试,并且我正在测试一个从使用泛型的类扩展而来的具体类。

服务层的示例设置如下:

public abstract class AbstractService <E extends AbstractEntity, IT extends AbstractItem> {

    public void deleteAllItems(E entity) {
        List<IT> items = new ArrayList<IT>(entity.getItems());
        for(IT item : items) {
            //Yada, yada
        }
    }
}

public class Service extends AbstractService<Entity, Item> {

}

public class OtherService() {
    @Inject
    private ServiceManager serviceManager;

    public void deleteItems(Entity e) {
        serviceManager.getService().deleteAllItems(e);
    }
}

然后进行测试,我有以下内容:

public class Test {
    private Service service;
    private OtherService otherService;
    private ServiceManager serviceManager;

    @BeforeMethod
    public void setup() {
        serviceManager= mock(serviceManager.class);
        service= mock(Service.class);
        when(serviceManager.getService()).thenReturn(service);
        otherService=injector.getInstance(OtherService.class);
    }

    @Test
    public void test() {
        Entity e = new Entity();
        //Attach some items
        otherService.deleteItems(e);
        verify(service).deleteAllItems(e);
    }
}

这应该调用存在的OtherService(我们正在使用注入来获取对象),然后调用方法deleteItems(),该方法又应该调用Service 上的deleteAllItems()。在我实现 Java 泛型之前,这工作正常,但由于我已经实现了 Java 泛型,Mockito 测试失败并出现以下异常:

java.lang.NoSuchMethodError: Service.deleteAllItems(实体;)V 在 Test.test(Test.java:XXX) org.mockito.exceptions.misusing.UnfinishedVerificationException: 此处缺少验证(模拟)的方法调用: -> 在 Test.test(Test.java:XXX)

正确验证示例: 验证(模拟).doSomething()

此外,此错误可能会出现,因为您验证了以下任一情况: final/private/equals()/hashCode() 方法。那些方法不能 存根/验证。

听起来好像找不到方法。我应该改为模拟 AbstractService 的抽象类还是我还缺少其他东西?

编辑

根据我对 Mockito 内部运作的了解,它创建了一个这样的实例:

public void AbstractService.deleteAllItems(Entity)

对于MockitoMethod 对象,Service.deleteAllItems()“未被调用”是有道理的,看起来 Mockito 假设只有基类被调用过。所以看起来我需要模拟基类。我将进一步调查,但如果有人有任何其他想法,我愿意接受建议

【问题讨论】:

  • 对于单元测试,我建议不要使用Injector。使用包私有构造函数创建被测类的实例。
  • 这个@NamshubWriter 有什么具体原因吗?我们使用 Injection 来控制所使用的对象的实例,它对我们来说工作正常。将模拟对象绑定到注入非常简单,然后允许Injector 完成插入正确类的工作。我们只在这一个变体上遇到了问题。另外,如何解决多层注入?如果您不小心,可能会编写很多模拟规则,通过注入我们可以将其设置在一个位置,其他对象将使用这些实例。
  • 依赖注入非常适合您的生产服务器/二进制文件和大型集成测试。对于单元测试,它会使您的测试变得缓慢、脆弱且难以理解。它还可以隐藏代码异味(例如具有太多依赖项的类)。测试应遵循 KISS

标签: java unit-testing generics mockito


【解决方案1】:

我可以建议将问题本地化 - 要么是在模拟中:

@Test
public void test() {
    Entity e = new Entity();
    service.deleteItems(e); // Note! 'service' itself, not an 'otherService'
    verify(service).deleteAllItems(e);
}

或注入(移除继承和泛型):

public class Service /*extends AbstractService<Entity, Item>*/ {
    public void deleteAllItems(Entity entity) {
        //...
    }
}

反复分解问题,你会找到原因。

【讨论】:

  • 我们之前已经在没有 Abstracts 的情况下使用它,所以我知道它之前在没有使用泛型的情况下也可以使用。但是,我们现在需要它来处理摘要和泛型,因为这是我们正在努力的方向,我有更多信息,但会在我周一在办公室时发布
【解决方案2】:

当您创建泛型类的非泛型子类时,Java 会为使用泛型类型的任何方法创建“桥接方法”。桥接方法看起来像继承的方法,但使用为泛型参数指定的特定类而不是泛型。

Java 创建这些方法是因为子类的方法不是泛型的,因此它们需要“看起来像”非泛型方法(即不受擦除、反射将按预期工作等)。详情请见this answer

解决方案是让 Mockito 模拟 serviceManager.getService() 返回的类型。

【讨论】:

  • 你的意思是像这些行:serviceManager= mock(serviceManager.class); service= mock(Service.class); when(serviceManager.getService()).thenReturn(service); 或者你的意思是模拟不同的类型?
  • @Draken 你没有显示getService() 的签名所以我不能说你应该模拟哪种类型
  • getService() 只返回Service的类型
  • @Draken 所以ServiceManager.g etService() 的签名是“Service getService()”? ServiceManager 注入了什么类型?
  • 什么类型的服务? Service 再次。至于模拟,这是通过:service= mock(Service.class); when(serviceManager.getService()).thenReturn(service); 完成的。为什么我们有泛型和抽象类有一个单独的区域。我们目前正在进行主要的代码重构,因此并非所有内容都已实现为在需要时使用抽象对象
【解决方案3】:

经过进一步调查,我找到了一种方法来强制 Mockito 调用正确的类。正如我简要提到的,我们使用注入来获取对象。在设置过程中,我们确实运行了喷油器的设置,我认为这不是导致问题的原因。但它确实提出了一个解决方案。我们就是这样称呼它的:

Injector injector = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                service = mock(Service.class);
                binder.bind(Service.class).
                    toInstance(service);
}

为了解决这个问题,我们只是将AbstractService 类绑定到Service 类的模拟实例,如下所示:

Injector injector = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                service = mock(Service.class);
                binder.bind(Service.class).
                    toInstance(service);
                binder.bind(AbstractService.class).
                    toInstance(service);                    
}

所以现在,当 Mockito 尝试获取 AbstractService 的实例时,它会调用模拟的 Service 并解决我们的问题。

如果有人有任何反馈,如果有替代解决方案,请随时发布,我可以对其进行测试,并检查是否有比我们正在做的更好的方法。

【讨论】:

  • 如果您删除与 Service.class 的绑定会发生什么失败?我开始怀疑你有一些使用AbstractService的构造函数的类,而另一些使用Service
  • @NamshubWriter 我们目前没有使用AbstractService 的构造函数,目前唯一一次使用它是通过导入,这是我在上面实现的单个修复以及Service 类的扩展。我们才刚刚开始重构代码,所以AbstractService 仅由Service 使用。 NewService 稍后会出现。删除与Service 的绑定并只留下AbstractService 的工作很有趣。我认为这是因为仅在 Service 中可用的其他方法目前尚未接受测试,而 Mockito 使用 AbstractService 的实例可以正常工作
  • 如果它在没有绑定到Service 的情况下工作,那么注入的类将采用AbstactService
  • 至于他们为什么这样做,我不知道。正如我所说,它没有被注入任何地方,只有具体的类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 2012-03-01
  • 1970-01-01
  • 2020-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多