sascha
sascha

Reputation: 79

Mockito does not recognize class

I tried to user weld-junit5 with mocking a class. I mock the class because i want to know how often it will be called.

But each time i try to Mockito.verify() this mocked class it throws a "NotAMockException"

The Intellij Debugger is validating the field as: "Mock for MessageService, hashCode: XY"

I already tried to add my testclass into the WeldInitiator but it dont want to work.

"MessageService" is a real class, not an interface (a Interface wouldn't work either)

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());
  }
}

I expect, that the Injected MessageService is a real mock, on which i can call every Mockito function, but it seems not so.

Do I anything wrong, or what is the proper way to do this?

I think I just resolved this problem:


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

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

Upvotes: 1

Views: 2070

Answers (2)

Nikos Paraskevopoulos
Nikos Paraskevopoulos

Reputation: 40298

As a user of JUnit 5 + Weld-JUnit myself, I am using the following pattern. The reasons are those explained in the answer by 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());
    }
}

Explanations:

  1. I am using the Mockito extension for convenience
  2. You do not really need to give it application scope
  3. The @Mock annotation is handled by the MockitoExtension. Again, this is only for convenience, you could create the mock yourself in a producer method.
  4. You do NOT need to inject the mock service; you have the messageServiceMock! The injected thing here will be the Weld proxy, as explained by Siliarus.

    It is fun to describe this injection a bit more: If the bean is @ApplicationScoped, i.e. "normal-scoped", CDI has to inject a proxy. If you use this proxy instead of the actual mock, you will get the exception. If you follow my advice from (2) and leave out the @ApplicationScoped, the bean will be dependent-scoped and the mock is injected directly. In this case you could use the injected field, but why bother?

  5. Use the mock directly.

Upvotes: 1

Siliarus
Siliarus

Reputation: 6753

To give some background, the way it works is that Weld lets Mockito create the desired object and then takes that as a contextual bean instance.

However, in CDI any bean that has normal scoped needs to have a proxy that is passed around instead of that instance. So what your producer actually does (because it is @ApplicationScoped) is create the object, store that in the context and then also create a proxy and pass around that proxy instead. The proxy is a different object (a delegate with no state) that "knows" how to get reference to the actual instance.

So what happens is that proxy gets injected into the field and you are checking the proxy object with Mockito.verify() call. Obviously, the proxy isn't the mock itself and so it fails. As user @second suggested, Weld offers an API to unwrap the proxy and get the contextual instance. I would argue that the API isn't "ugly", it is just a thing users shouldn't mostly care about but sometimes you cannot avoid it.

You could avoid having proxy by using some of the pseudo scopes which are @Dependent or CDI @Singleton. With that it should work as well and so long as it's for tests, replacing application scoped with singleton should work.

As for your workaround, I do no see how that solves anything - it is basically the same producer and because of the scope it will only be invoked once, hence the static field will make no difference (as there will be a singular call to mock creation). Have you changed something else than that?

Upvotes: 4

Related Questions