weld/weld-testing

Problems with injecting Mockito Mocks into WELD

moroff opened this issue · 7 comments

I try to combine weld-junit with mockito. Here are my simple classes

  1. The mocked interface
public interface TestInterface {

	String call(String parameter);
}
  1. The Simple Service, using the interface
public class ClassUnderTest {

	@Inject // @Dependent
	TestInterface testInterface;
	
	public String callInterface(String parameter) {
		return testInterface.call(parameter);
	}
	
}
  1. The JUnit Test
@ExtendWith(WeldJunit5Extension.class)
class MockitoWeldInitiatorTest {

	@WeldSetup
	WeldInitiator weldInstantor = MockitoWeldInitiator //
			.from(getClass(), ClassUnderTest.class)//
			.inject(this)//
			.build();

	@Produces
	@ApplicationScoped
	TestInterface interfaceMock = mock(TestInterface.class, withSettings().verboseLogging());

	@Inject
	ClassUnderTest classUnderTest;

	@Test
	void testValid() {
		// Arrange
		when(interfaceMock.call("ValidParameter")).thenAnswer((i) -> {
			System.out.println("--> mock: " + interfaceMock.toString());
			return "ValidParameter";
		});

		// Act
		String result = classUnderTest.callInterface("ValidParameter");

		// Assert
		assertEquals("ValidParameter", result);
		verify(interfaceMock);
	}
}

The test fails, the output is

Mär 13, 2022 9:11:01 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
INFO: WELD-000900: 4.0.0 (Final)
Mär 13, 2022 9:11:01 PM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
Mär 13, 2022 9:11:02 PM org.jboss.weld.environment.se.WeldContainer fireContainerInitializedEvent
INFO: WELD-ENV-002003: Weld SE container 30312847-67f9-4792-a4b1-4eef11bfb28e initialized
############ Logging method invocation #1 on mock/spy ########
testInterface.call("ValidParameter");
   invoked: -> at org.jboss.weld.junit5.mockito.MockitoWeldInitiatorTest.testValid(MockitoWeldInitiatorTest.java:46)
   has returned: "null"

############ Logging method invocation #1 on mock/spy ########
testInterface.call("ValidParameter");
   invoked: -> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   has returned: "null"

Mär 13, 2022 9:11:02 PM org.jboss.weld.environment.se.WeldContainer shutdown
INFO: WELD-ENV-002001: Weld SE container 30312847-67f9-4792-a4b1-4eef11bfb28e shut down

It seems there are different instances of the mock uses the one injected into to service and the other one used in the test. How could I achieve to get the same instance of the mock.

Hello

In the documentation there are several ways to achieve mocking, see https://github.com/weld/weld-junit/blob/master/junit5/README.md#adding-mock-beans

Your looks very much like the producer approach we have there.

It seems there are different instances of the mock uses the one injected into to service and the other one used in the test. How could I achieve to get the same instance of the mock.

Not sure I fully understand you but I think the problem lies with referencing the interfaceMock field directly in the test.
CDI makes sure that TestInterface is a single instance of an app scoped bean but can only do that if you retrieve the bean instance via CDI means (typically injection or dynamic resolution).

Instead, try something like this:

@Test
	void testValid() {
                TestInterface interfaceBean = weldInstantor.getBeanManager().createInstance().select(TestInterface.class).get();
		// Arrange
		when(interfaceBean.call("ValidParameter")).thenAnswer((i) -> {
			System.out.println("--> mock: " + interfaceBean.toString());
			return "ValidParameter";
		});

		// Act
		String result = classUnderTest.callInterface("ValidParameter");

		// Assert
		assertEquals("ValidParameter", result);
		verify(interfaceBean);
	}

One other note - you marked your bean as @ApplicationScoped which is a normal scope and indicated that a proxy will be created. This will likely interfere with mockito verification in verify(interfaceBean); because the reference is a proxy object and no an actual bean. In this case you could use the field reference or unwrap the proxy to get your hands on the underlying contextual instance. You can do that via Weld API as every proxy object is an instance of WeldClientProxy.

Hello Matej,

Thanks, changing the verification helps, my code look now like this:

`
@ExtendWith(WeldJunit5Extension.class)
class MockitoWeldInitiatorTest {

@WeldSetup
WeldInitiator weldInstantor = MockitoWeldInitiator //
		.from(getClass(), ClassUnderTest.class)//
		.inject(this)//
		.build();

@Produces
@ApplicationScoped
TestInterface interfaceMock = mock(TestInterface.class, withSettings().verboseLogging());

@Inject
ClassUnderTest classUnderTest;

@Test
void testValid() {
	TestInterface interfaceMock = (TestInterface) ((WeldClientProxy)weldInstantor.getBeanManager() //
                       .createInstance().select(TestInterface.class).get()).getMetadata().getContextualInstance();
	
	// Arrange
	when(interfaceMock.call("ValidParameter")).thenAnswer((i) -> {
		System.out.println("--> mock: " + interfaceMock.toString());
		return "ValidParameter";
	});

	// Act
	String result = classUnderTest.callInterface("ValidParameter");

	// Assert
	assertEquals("ValidParameter", result);
	
	verify(interfaceMock).call(Mockito.anyString());
}

`
In the next few days I will come up with a Junit extension that will simplify this handling. I will post a pull request.

TestInterface interfaceMock = (TestInterface) ((WeldClientProxy)weldInstantor.getBeanManager() //
.createInstance().select(TestInterface.class).get()).getMetadata().getContextualInstance();

@moroff You shouldn't perform proxy unwrapping for standard method calls. In fact, you shouldn't perform it overall but if you absolutely must, do it only for the Mockito verify() invocation. Every other invocation you mentioned should be fine without it.

Or, if you just don't want proxy at all, replace @ApplicationScoped with @jakarta.inject.Singleton - it is also a single instance in whole app but without any proxy.

Or, if you just don't want proxy at all, replace @ApplicationScoped with @jakarta.inject.Singleton - it is also a single instance in whole app but without any proxy.

Changing to

@Produces @Singleton TestInterface interfaceMock() { return mock(TestInterface.class, withSettings().verboseLogging()); }

will result in an exception

`
org.jboss.weld.exceptions.DeploymentException: WELD-001443: Pseudo scoped bean has circular dependencies. Dependency path:

  • Managed Bean [class org.jboss.weld.junit5.mockito.MockitoWeldExtensionTest] with qualifiers [@Any @default],
  • [BackedAnnotatedField] @Inject org.jboss.weld.junit5.mockito.MockitoWeldExtensionTest.classUnderTest,
  • Managed Bean [class org.jboss.weld.junit5.mockito.ClassUnderTest] with qualifiers [@Any @default],
  • [BackedAnnotatedField] @Inject org.jboss.weld.junit5.mockito.ClassUnderTest.testInterface,
  • Producer Method [TestInterface] with qualifiers [@Any @default] declared as [[BackedAnnotatedMethod] @produces @singleton org.jboss.weld.junit5.mockito.MockitoWeldExtensionTest.interfaceMock()],
  • Managed Bean [class org.jboss.weld.junit5.mockito.MockitoWeldExtensionTest] with qualifiers [@Any @default]
    `

@moroff You shouldn't perform proxy unwrapping for standard method calls. In fact, you shouldn't perform it overall but if you absolutely must, do it only for the Mockito verify() invocation. Every other invocation you mentioned should be fine without it.

What is the reason for this? In my case I want to deal with mockito mocks, and my goal is to have them injected into the implementation and use these mocks in the unit test.

With me extension the unit test is quite straight forward.

`

@ExtendWith(WeldJunit5Extension.class)
@ExtendWith(MockitoWeldExtension.class)
class MockitoWeldExtensionTest {

@WeldSetup
WeldInitiator weldInstantor = WeldInitiator //
		.from(getClass(), ClassUnderTest.class)//
		.inject(this)//
		.build();

@Inject
ClassUnderTest classUnderTest;

@Produces
@Singleton
TestInterface interfaceMock() {
	return mock(TestInterface.class, withSettings().verboseLogging());
}

@InjectMock
TestInterface interfaceMock;

@Test
void testValid() {
	// Arrange
	when(interfaceMock.call("ValidParameter")).thenAnswer((i) -> {
		System.out.println("--> mock: " + interfaceMock.toString());
		return "ValidParameter";
	});

	// Act
	String result = classUnderTest.callInterface("ValidParameter");

	// Assert
	assertEquals("ValidParameter", result);
	
	verify(interfaceMock).call(Mockito.anyString());
}

`

I intoduced MockitoWeldExtension and @InjectMock. You find my implementation in the fork https://github.com/moroff/weld-junit

org.jboss.weld.exceptions.DeploymentException: WELD-001443: Pseudo scoped bean has circular dependencies. Dependency path:

Well, yes.
Because with current state you have the producer (@Produces @Singleton TestInterface interfaceMock()) and the injection point (@Inject ClassUnderTest) for the same bean in one class. And CDI needs to be able to instantiate the class where the producer is to be able to invoke that method. However, in order to instantiate the class, it needs to satisfy all injection points in it - this is a circular dependency problem. If you move the producer to a separate bean class, it would work. Or you could @Inject Instance` and perform dynamic resolution during runtime which would also prevent this dependency; at least I think it should.

This is defined here with:

The container is required to support circularities in the bean dependency graph where at least one bean participating in every circular chain of dependencies has a normal scope, as defined in Normal scopes and pseudo-scopes. The container is not required to support circular chains of dependencies where every bean participating in the chain has a pseudo-scope.

For the second part of your comment:

@moroff You shouldn't perform proxy unwrapping for standard method calls. In fact, you shouldn't perform it overall but if you absolutely must, do it only for the Mockito verify() invocation. Every other invocation you mentioned should be fine without it.

What is the reason for this? In my case I want to deal with mockito mocks, and my goal is to have them injected into the implementation and use these mocks in the unit test.

Proxies are an internal construct and ideally, users shouldn't even be aware that a proxy is in play. That's why CDI API has no means of "unwrapping" a proxy object and I directed you to Weld APIs instead. Sometimes, there is no way around it which is why we have this API after all but in your case, the proxy is completely superfluous apart from solving the circular dependency issue which is itself a rather unfortunate coding pattern :-)

I intoduced MockitoWeldExtension and @InjectMock. You find my implementation in the fork https://github.com/moroff/weld-junit

So, this extension basically only performs dynamic resolution and unwrapping with the resolution actually bypassing the circular dependency problem. BTW if you invoke that code for a CDI object without proxy, it will crash - you need to check if the object is instanceof WeldClientProxy before casting. Anyway, I don't think that the extension generally useful and you can replace the whole functionality with the tips I mentioned earlier. However, I understand that it might be helpful for your projects if you are doing tons of these kinds of tests.

I moved the producer methods into a static inner class, now I'm happy with that, thanks for the help. Maybe to documentation should mention this problem. Here is my code:

`
@ExtendWith(WeldJunit5Extension.class)
class MockitoWeldExtensionPerMethodTest {

/**
 * Creating mocks in a separate class to avoid circular dependency problems.
 */
static class MockProducer {
	@Produces
	@Singleton
	TestInterface interfaceMock() {
		return mock(TestInterface.class, withSettings().verboseLogging());
	}
}

@WeldSetup
WeldInitiator weldInstantor = WeldInitiator //
		.from(getClass(), MockProducer.class, ClassUnderTest.class)//
		.inject(this)//
		.build();

@Inject
ClassUnderTest classUnderTest;

@Inject
TestInterface interfaceMock;

@Test
void testValid() {
	// Arrange
	when(interfaceMock.call("ValidParameter")).thenAnswer((i) -> {
		System.out.println("--> mock: " + interfaceMock.toString());
		return "ValidParameter";
	});

	// Act
	String result = classUnderTest.callInterface("ValidParameter");

	// Assert
	assertEquals("ValidParameter", result);
	
	verify(interfaceMock).call(Mockito.anyString());
}

`

Glad I could help.

Maybe to documentation should mention this problem.

I see what you mean but I am not sure how to mention it here without writing an essay. It only concerns using this extension along with Mocks and then only if you have proxies in play and on top of that only if you try to declare producer and injection point in a single class :-)

I'll close this issue as resolved then.