Mockito无法识别课程

时间:2019-08-10 15:26:13

标签: java mockito cdi weld-junit5

我试图通过模拟类来使用weld-junit5。 我嘲笑该类是因为我想知道它将多久被调用一次。

但是每次我尝试Mockito.verify()这个模拟的类时,它都会引发“ NotAMockException”

Intellij调试器将字段验证为:"Mock for MessageService, hashCode: XY"

我已经尝试将我的测试类添加到WeldInitiator中,但它不想工作。

“ MessageService”是一个真实的类,而不是一个接口(一个Interface也不起作用)

Docs

@EnableWeld
class GameServiceTest {

  @WeldSetup
  public WeldInitiator weld = WeldInitiator.from(GameService.class, GameCache.class,
                                                 /** Some More **/,
                                                 GameServiceTest.class).build();
  @Produces
  @ApplicationScoped
  public MessageService createMessageServiceMock() {
    return mock(MessageService.class);
  }

  @Inject
  private MessageService messageService;
  @Inject
  private GameService gameService;

  @Test
  void handleRunningGames() {
    this.gameService.handleRunningGames(null, mock(Session.class));
    // This will throw a org.mockito.exceptions.misusing.NotAMockException
    Mockito.verify(messageService, Mockito.times(1)).writeMessage(any(), any());
  }
}

我希望Injected MessageService是一个真实的模拟,我可以在其上调用每个Mockito函数,但事实并非如此。

我有什么问题吗,或者做这件事的正确方法是什么?

我想我已经解决了这个问题:


  private static final MessageService messageService = mock(MessageService.class);

  @Produces
  @ApplicationScoped
  public MessageService createMessageServiceMock() {
    return messageService;
  }

2 个答案:

答案 0 :(得分:3)

为了提供一些背景知识,其工作方式是Weld让Mockito创建所需的对象,然后将其用作上下文Bean实例。

但是,在CDI中,任何具有正常作用域的bean都需要具有传递的代理而不是该实例。因此,生产者实际要做的事情(因为它是@ApplicationScoped)是创建对象,将其存储在上下文中,然后还创建一个代理并传递该代理。代理是一个不同的对象(无状态的委托),它“知道”如何获得对实际实例的引用。

所以发生的事是代理被注入到字段中,并且您正在通过Mockito.verify()调用来检查代理对象。显然,代理不是模拟本身,因此失败。正如用户@second所建议的那样,Weld提供了一个API来解包代理并获取上下文实例。我认为该API并不“丑陋”,这只是用户最不应该关心的事情,但有时您无法避免。

您可以通过使用一些伪作用域来避免代理,这些伪作用域为@Dependent或CDI @Singleton。有了它,它应该也能正常工作,并且只要用于测试,就应该用单例替换作用域内的应用程序。

关于您的解决方法,我看不到如何解决任何问题-它基本上是同一生产者,并且由于范围,它只会被调用一次,因此静态字段不会有任何区别(因为会有一个单数形式)调用以模拟制作)。除了此以外,您还有其他改变吗?

答案 1 :(得分:1)

作为我自己的JUnit 5 + Weld-JUnit用户,我正在使用以下模式。原因由Siliarus的答案解释。

import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.Mock;
import ...

@EnableWeld
@ExtendWith(MockitoExtension.class) // (1)
class GameServiceTest {

    @WeldSetup
    public WeldInitiator weld = WeldInitiator.from(GameService.class, GameCache.class,
                            /** Some More **/,
                            GameServiceTest.class).build();
    @Produces
    @ApplicationScoped // (2)
    @Mock              // (3)
    private MessageService messageServiceMock;

//  @Inject            // (4)
//  private MessageService messageService;
    @Inject
    private GameService gameService;

    @Test
    void handleRunningGames() {
        this.gameService.handleRunningGames(null, mock(Session.class));
        // (5)
        Mockito.verify(messageServiceMock, Mockito.times(1)).writeMessage(any(), any());
    }
}

说明:

  1. 为了方便起见,我正在使用Mockito扩展
  2. 您真的不需要为其提供应用范围
  3. @Mock注释由MockitoExtension处理。同样,这只是为了方便起见,您可以在生产方法中自己创建模拟。
  4. 您不需要注入模拟服务;您拥有messageServiceMock!正如Siliarus解释的那样,这里注入的将是Weld代理。

    对此注入进行更多的描述很有趣:如果bean是@ApplicationScoped,即“正常范围”,则CDI必须注入代理。如果使用此代理而不是实际的模拟,您将获得异常。如果您遵循我在(2)中的建议,而忽略了@ApplicationScoped,那么该bean将是依赖范围的,并且将直接注入该模拟。在这种情况下,您可以使用注入的字段,但是为什么要麻烦呢?

  5. 直接使用模拟。