wcm io AemContextExtension mocking LanguageManager in JUnit 5 - testing

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

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.

Kotlintest extensions providing information back to the test

JUnit 5 has a neat extensions functionality which is not compatible with kotlintest even if it runs on JUnit framework. While the simple use cases in which we just need to log something can be handled by the TestListener, we cannot handle more advanced cases. In particular, how to interact with the extension? Ideally, I would like to get a hold of the extension so I could query it.
In JUnit5 it would be (one of the options anyway)
#ExtendWith(MyExtension.class)
class Something() {
#MyAnnotation
MyType myType;
#Test
void doSomething() {
myType.doSomething();
}
}
In JUnit4 it would be even simpler
#Rule
MyRule myRule;
#Test
void fun() {
myRule.something();
}
Of course, there is a SpringExtension but it does the reflective instantiation of the class.
Is there any way to do it easier?
You can keep a reference to an extension/listener before passing it to the overriden function.
For example:
val myListener = MyKotlinTestListener()
val myOtherListener = MyOtherKotlinTestListener()
override fun listeners() = listOf(myListener, myOtherListener)
This way you can do what you want in your tests with that reference available
test("MyTest") {
myListener.executeX()
}
The same goes if you're using an extension of any sort.
They'll still be executed as part of KotlinTest's lifecycle!

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...

unit testing extension function and mocking the other methods of the class

I am writing an extension function adding some retry capabilities to AmazonKinesis.putRecords. In my extension method i do some logic and some calls to the original putRecords method:
fun AmazonKinesis.putRecordsWithRetry(records: List<PutRecordsRequestEntry>, streamName: String) {
//...
val putRecordResult = this.putRecords(PutRecordsRequest().withStreamName(streamName).withRecords(records))
//...
}
From a unit test point of view I am finding it hard to see how I should mock the call to this.putRecords
I am using com.nhaarman.mockitokotlin2.*
val successfulRequest = PutRecordsResultEntry().withErrorCode(null);
class KinesisExtensionTest : StringSpec({
val testRecords = ArrayList<PutRecordsRequestEntry>()
testRecords.add(PutRecordsRequestEntry().withPartitionKey("iAmABunny").withData(ByteBuffer.wrap("aaa".toByteArray()))
)
val kinesis = mock<AmazonKinesis>{
on { putRecordsWithRetry(testRecords, "/dev/null") }.thenCallRealMethod()
on { putRecords(any()) }.thenReturn(PutRecordsResult().withRecords(listOf(successfulRequest, successfulRequest)))
}
"can write a record" {
kinesis.putRecordsWithRetry(testRecords, "/dev/null")
verify(kinesis).putRecord(any())
}
})
The putRecordResult is always null
The extension function AmazonKinesis.putRecordsWithRetry will be compiled to a static function under the hood, and Mockito doesn't support static method mocking yet.
Therefore Mockito may not know the stubbing information at the verification step and thus the default null value is produced.

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

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