Mocking a private method in an Expectations block causes "IllegalStateException: Missing invocation to mocked type at this point" - jmockit

I have a JUnit test that uses a JMockit Expectations block:
#Tested
private MyTestedClass myTestedClass;
#Injectable
private MyOtherClass myOtherClass;
#Test
public void publicBooleanMethodTest() {
new Expectations() {{
invoke(myOtherClass, "privateMethod");
result = true;
}};
myTestedClass.run(); // Calls myOtherClass.privateMethod();
}
However, this results in the following error:
java.lang.IllegalStateException: Missing invocation to mocked type at this point; please make sure such invocations appear only after the declaration of a suitable mock field or parameter
As you can see, myClass is mocked via #Injectable, so I'm not sure why the error is happening. I have also found that if I change the scope of privateMethod() to public, everything works.
What is going on and how can I fix this? It worked fine in JMockit 1.22, but now fails in JMockit 1.23 and greater.

JMockit 1.23 removed support for mocking private methods in Expectations blocks. From the release notes:
Version 1.23 (Apr 24, 2016):
Dropped support for the mocking of private methods/constructors when using the Expectations API, to prevent misuse. If still needed, they can be mocked or stubbed out with the application of a MockUp<T>.
Unfortunately, the erroneous error message is currently seen as "too costly too fix" by the development team. A better error message was added in JMockit 1.28 as a result of this discussion.
The error log says to use MockUp<T> as an alternative. In this case, the code would be as follows:
#Tested
private MyTestedClass myTestedClass;
#Injectable
private MyOtherClass myOtherClass;
#Test
public void publicBooleanMethodTest() {
// Partially mock myOtherClass
new MockUp<MyOtherClass>(myOtherClass) {
#Mock boolean privateBooleanMethod() {
return true;
}
};
myTestedClass.run(); // Calls myOtherClass.privateMethod();
}

Related

Why does Mockito throw an "InvalidUseOfMatchersException" at for this code?

I am writing a test for a Spring-Boot project written in Kotlin 1.5. This piece of code does fail at test runtime with an InvalidUseOfMatchersException and I struggle to figure out why:
#SpringBootTest
#AutoConfigureMockMvc
internal class ControllerTest {
#Autowired
lateinit var mockMvc: MockMvc
#MockBean
lateinit var mockedAuthFilter: AuthFilter
#BeforeEach
fun setup() {
assertNotNull(mockedAuthFilter)
`when`(
mockedAuthFilter.shouldProceed(
any(HttpServletRequest::class.java),
any(AuthConfig::class.java)
)
).thenReturn(true)
}
#Test
fun `This call should return without an error`() {
mockMvc.perform(get("/api/entities")).andDo(print()).andExpect(status().isOk)
}
}
All I can find in the web for this error is that you tried an argument matcher on a basic type - but that is not the case here. Have you faced this problem and how did you solve it?
From my understanding the failure comes from a masked nullpointer exception caused by the shouldProceed method itself which doesn't allow a call with nulled arguments, which kind of happens internally in Mockito during the setup of the mocked instance after any() returns.
The solution was to not use Mockito with Kotlin, but the MockK test utility instead.

mockk, clearAllMocks or unmockkAll

mockk 1.9.3
In the test noticed if did static mock in previous test, the next test will be using same mock.
Thought to do a reset at #After, but not sure which one to use clearAllMocks or unmockkAll.
in https://mockk.io/
unmockkAll unmocks object, static and constructor mocks
clearAllMocks clears regular, object, static and constructor mocks
but not clear what are the difference by unmocks and clears.
e.g.
#Test
fun test_1() {
mockkStatic(TextUtils::class)
every { TextUtils.isEmpty(param } returns true
//test
doSomeThingUsingTextUtils()
// verify
... ...
}
#Test
fun test_2() {
// in this test it does not want the mocked stub behavior
}
What it should use, clear or 'unmock`?
For me, understanding the difference between Clearing and Unmocking was sufficient.
clear - deletes internal state of objects associated with mock
resulting in empty object
unmock - re-assigns transformation of
classes back to original state prior to mock
(Source)
PS: I understand the confusion! I had it as well!
Let me know if you have any questions. Thanks.

wcm io AemContextExtension mocking LanguageManager in JUnit 5

I just upgraded our AEM codebase to use JUnit 5 but we are having some issues with some tests mocking specific methods of the LanguageManager.
#MockitoSettings(strictness = Strictness.LENIENT)
#ExtendWith({AemContextExtension.class, MockitoExtension.class})
public class SomeComponentTest {
#Mock
private LanguageManager languageManager;
#BeforeEach
public void setUp() throws Exception {
...
context.registerService(LanguageManager.class, languageManager);
...
}
#Test
public void someTest() {
...
Page mockPage = mock(Page.class);
given(languageManager.getLanguageRoot(any())).willReturn(mockPage);
...
}
}
Even though I register the LanguageManager mock it still seem to get the WCM IO provided mock, which is initialized here: https://github.com/wcm-io/wcm-io-testing/blob/develop/aem-mock/core/src/main/java/io/wcm/testing/mock/aem/context/AemContextImpl.java -> registerInjectActivateService(new MockLanguageManager());
Am I doing something wrong here or is there just no way to spy / mock the methods of the LanguageManager. Do note that in the past this worked when using:
#RunWith(MockitoJUnitRunner.class)
I got an answer over here: https://wcm-io.atlassian.net/browse/WTES-58?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=21269#comment-21269
context.registerService(LanguageManager.class, myCustomLanguageManager,
Constants.SERVICE_RANKING, 100);
Or
this.mockLanguageManager = context.registerService(LanguageManager.class,
spy((MockLanguageManager)context.getService(LanguageManager.class)),
Constants.SERVICE_RANKING, Integer.MAX_VALUE);
// Now you can leverage the mocked language manager that is provided, while
you can still override some of the methods if desired, due to it being a spy

How to correctly use Mockito's verify on a spring #Service test

I have this service (all in kotlin):
#Service
class MyService {
fun getSomeString(): String = "test"
}
And this integration test class:
#RunWith(SpringRunner::class)
#SpringBootTest
#EmbeddedKafka // used on some kafka tests
class BasicTests {
and method:
#Test
fun `test method count`() {
// here I have a kafka producer sending a message to a topic that ends up
// calling myService.getSomeString via #KafkaListener from other services
verify(someObjectRelatedToMyService, atLeast(1)).getSome()
}
In the place of someObjectRelatedToMyService I tried to use
#Autowired
lateinit var myService: MyService
But then I got Argument passed to verify() is of type MyService and is not a mock!
But when I use
#Mock
lateinit var myMock: MyService
I get Actually, there were zero interactions with this mock.
And actually, to me it makes sense, since my mock wasn't called, but my real service at the application was.
Is it possible to count method calls from my real object?
You can spy on the real object to count method calls on it like this:
#Test
fun `test method count`() {
Mockito.spy(someObjectRelatedToMyService)
verify(someObjectRelatedToMyService, atLeast(1)).getSome()
}
As you can see, the only thing you have to do is to call the spy method which enables tracking interactions with the target object.
When adding this call before the verify method, you should not get the error anymore that the object is not a mock.
[Posting here since no rep to comment] Have you tried using a #Spy? Then you could specify which methods to mock and which methods to call. I supposed you can also apply Mockito.verify on spies...

JMockit basics: mocked object, mocked parameter, expectation

I just started using JMockit and I am a bit confused on some basics of JMockit, in terms of when mock object is created, mocked object scope and what is the effect of mock etc. Please help with the following questions.
My questions refer to the following code:
public class MyClassTest
{
#Mocked({"method1","method2"})
ClassA classA; //ClassA has only static method
#Mocked
ClassB classB;
#Test
public void test1()
{
new NonStrictExpectations() {{
MyClassA.method3(classB);
result = xxx;
}};
// testing code
....
// verification
...
}
#Test
public void test2(#Mocked ClassC classC)
{
...
}
}
Questions:
About #Mocked declared as a instance variable for a junit test, like #Mocked ClassB classB:
(1) For junit, the instance variable is newly created for each test (like test1(), test2()), right? Is it true that before each test runs, a new mocked instance of ClassB is created?
(2) It mocks the class. It makes all methods in ClassB mocked for all tests (test1() and test2() in this case), right?
(3) If methods are specified for mocked object, like "#Mocked({"method1","method2"}) ClassA classA;", it means only method1 and method2 can be mocked? Can other methods be added to be mocked in Expectations for a test?
About #Mocked parameter passed in to test, like "#Mocked ClassC classC" for test2:
I assume this mock should not affect other tests? Is it true that ClassC is only mocked in test2()?
Expectation:
(1) For expectation specified in a test, is its scope local to the test, meaning the mocked method is only effective in this test? For example, ClassA.method3() is only mocked in test1(), right?
(2) The recorded method in expectation only runs when the matching method is invoked from the testing code, is it? If recorded method parameter does not match, will it run the real method?
I am getting an exception in ClassA.method3() when running test1(). Somehow the real method of ClassA.method3() is executed and gave exception. I guess it is due to parameter mismatch for ClassA.method3()?
Answering your questions:
(1) Yes; (2) yes; (3) yes, and other methods cannot be mocked in this same test class.
Yes, only in the test which has the mock parameter.
(1) Right, the expectation is only valid within the scope where it's recorded. (2) No, once mocked, the real implementation of a method is never executed.
As for the exception you get, I can't tell why it happens without seeing a complete test.