I am having some issue with my jUnit tests. They are running very slowly.
Context: I have around 100 unit tests that are taking more than 2 minutes to run!
I am mocking dependencies using mockk library. I noticed that the one that I mock dependencies are the slowest. I understand that mocking object can take some time, but that is just too much. I must have something wrong...
Here is the "pseudo code" for one of my unit test class:
internal class MyClassImplTest {
#get:Rule
val coroutineTestRule: CoroutineTestRule = CoroutineTestRule()
#MockK
lateinit var dependencie1: Dependencie1
#MockK
lateinit var dependencie2: Dependencie2
lateinit var myClassImpl: MyClassImpl
#Before
fun setUp() {
MockKAnnotations.init(this)
myClassImpl= MyClassImpl(
FakeCoroutinesDispatcherProvider(coroutineTestRule.testDispatcher),
dependencie1,
dependencie2
)
}
#Test
fun `should not be null`() {
assertNotNull(myClassImpl)
}
#Test
fun `should do something`() = coroutineTestRule.runBlockingTest {
//mock method call with coEvery {}
val isConnected = myClassImpl.connect(options)
assertFalse(isConnected)
//verify method calls with coVerify {}
}
#Test
fun `should do something else`() = coroutineTestRule.runBlockingTest {
//mock method call with coEvery {}
val isConnected = myClassImpl.connect(options)
assertFalse(isConnected)
//verify method calls with coVerify {}
}
}
For instance, this one was the one that took longer in the image above (38s).
This is not deterministic. It is not always the same that is the slowest, but when I run all of them it takes always more than 2 minutes to run...
So what am I missing here?
Related
I have a class that takes a coroutine dispatcher as a parameter that I am trying to test. In my tests I use #Before to setup my class before each test is run
#OptIn(ExperimentalCoroutinesApi::class)
#Before
fun setup() = runTest {
.....
val dispatcher = StandardTestDispatcher(testScheduler)
scheduleService = ScheduleService(dispatcher)
}
I have a test that I am trying to run that has a SharedFlow that I want to check the value of so I also use runTest with that test
#OptIn(ExperimentalCoroutinesApi::class)
#Test
fun testFullScheduleCreation() = runTest{
......
val data = scheduleService.scheduleChangedListener.first()
}
When I try to run the test I get an error
Detected use of different schedulers. If you need to use several test
coroutine dispatchers, create one TestCoroutineScheduler and pass it
to each of them.
The error is because of my use of #Before but I am not sure how to fix the error without copying all code in that setup method to each test
There are a few ways to share a dispatcher between tests. The simplest is to call Dispatchers.setMain(...) in your setup. If the main dispatcher is set to a TestDispatcher, the runTest function will use it for all subsequent tests.
#Before
fun setup() {
val dispatcher = StandardTestDispatcher()
scheduleService = ScheduleService(dispatcher)
Dispatchers.setMain(dispatcher)
}
If you use this approach, you should also call Dispatchers.resetMain() in a test teardown function.
If you prefer to do it manually, you can also pass the dispatcher to runTest.
lateinit var dispatcher: TestDispatcher
#Before
fun setup() {
dispatcher = StandardTestDispatcher()
scheduleService = ScheduleService(dispatcher)
}
#Test
fun myTest() = runTest(dispatcher) {
...
}
As an alternative, you can also call scope.runTest to use a shared scope that contains a dispatcher.
lateinit var scope: TestScope
#Before
fun setup() {
val dispatcher = StandardTestDispatcher()
scope = TestScope(dispatcher)
scheduleService = ScheduleService(dispatcher)
}
#Test
fun myTest() = scope.runTest {
...
}
From what I understand about mocking, the test should not go deeper into the bean being mocked. For example the control flow shouldn't go into the function apiService.getSomeData() and instead it should just return the string "Hello there".
But is that how mocking works or does the program keep going deeper and should I be able to see the print statements of getSomeData() in the stdout?
When I actually run the code below, it doesn't go deeper. But is that how it's supposed to work?
Suppose this is the Rest Controller Code:
#RestController
#RequestMapping(value = "/testing")
public class ApiController {
#Autowired
ApiService service;
#PostMapping(path = "/events/notifications",consumes = "application/json", produces = "application/json" )
public ResponseEntity<String> checkMapping(#Valid #RequestBody String someData, #RequestHeader(value="X-User-Context") String xUserContext) throws Exception {
String response = service.getSomeData(someData);
return ResponseEntity.status(HttpStatus.OK).body(response);
}
}
Suppose this is the Controller test code:
#WebMvcTest(ApiController.class)
public class ApiControllerTest {
#Autowired
MockMvc mockMvc;
#Autowired
ObjectMapper mapper;
#MockBean
ApiService apiService;
#Test
public void testingApi() throws Exception {
Mockito.when(apiService.getSomeData("")).thenReturn("Hello there");
MockHttpServletRequestBuilder mockRequest = MockMvcRequestBuilders.post("/testing/events/notifications")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.header("X-User-Context","something")
.content("something");
mockMvc.perform(mockRequest)
.andExpect(status().isBadGateway());
}
}
Suppose this is the Api Service code:
#Service
public class ApiServiceImpl implements ApiService{
#Override
public String getSomeData(String data) throws Exception {
System.out.println("Going deeper in the program flow);
callThisFunction();
return "Some data";
}
public void callThisFunction(){
System.out.println("Going two levels deeper");
}
}
In your test you are not talking to ApiServiceImpl at all, but an instance that is created by mockito and that is also implementing the ApiService interface. Therefore, your implementation of getSomeData() is not executed at all. That's what mocking is about. You create a "mock" implementation (or let a tool like mockito do it for you) of the thing you do not want to be executed and inject it instead of the "real" thing.
Eg.
#Component open class AService {
#Autowired private lateinit var anotherService: AnotherService
...
...
fun methodCall() {
anotherService.methodCall()
}
}
#Service
open class AnotherService{
#Autowired private lateinit var aRepo: ARepo
fun methodCall() {
aRepo.something()
}
}
#WebAppConfiguration
#ActiveProfiles("test")
#RunWith(SpringJUnit4ClassRunner::class)
#SpringBootTest(classes = [TestAppConfig::class])
open class MyTest {
#MockBean private lateinit var aRepo: ARepo
#SpyBean private lateinit var anotherService: AnotherService
#Autowired private lateinit var aservice: AService
#Before fun setUp() {
MockitoAnnotations.openMocks(this)
}
#Test
fun shouldNotInteractWithARepo() {
aservice.methodCall() // Call to Test
verifyNoInteractions(aRepo) // This works and fails as expected
//verifyNoInteractions(anotherService) // Expect to fail since there is actually a call being made on this spy but it passes.
}
aRepo does an external network call. I have mocked it at an application level. Similarly anotherService is the encapsulating service in which aRepo makes the network call. I have spied anotherService because I want to verify at times that there is no interaction with it and at times, I want its actual behavior.
In the examples shown, I have only considedred the first case of no interaction. Testing of the actual behavior with #Spy works fine.
verifyNoInteractions on the spied field(anotherService) always passes. Why is this ?. The same when I use it on the Mocked field(aRepo), it works and fails as expected.
How do I setup in a way that if anotherService.methodCall() happens, the test should fail ?
I'm writing integration test on a RestController in SpringBoot.
Normally I would run with SpringRunner.class, but when it comes to Mock a static method I need to use PowerMock.
The strange fact is that when I run the single tests, they individually pass (but returns error messages), when I try to run the entire test class, no test passes and it returns the same error message.
#RunWith(PowerMockRunner.class)
#PrepareForTest({JwtUtils.class})
//#PowerMockRunnerDelegate(SpringRunner.class) THIS DOESN'T WORK!!!
#SpringBootTest(classes = SpringBootJwtApplication.class)
public class RestAccessIntegrationTest {
#Autowired #InjectMocks
RestController restController;
#Mock
HttpServletRequest request;
#Test
public void operationsPerAccountWhenSuccessfulTest(){
mockStatic(JwtUtils.class);
when(JwtUtils.myMethod(request)).thenReturn("blabla");
String expected = ... ;
String actual = restController.getOperations();
assertEquals(actual, expected);
}
}
If I run the test or the entire class I get an error of this type:
Exception in thread "main" java.lang.NoSuchMethodError: org.powermock.core.MockRepository.addAfterMethodRunner(Ljava/lang/Runnable;)at org.powermock.api.mockito.internal.mockcreation.MockCreator.mock(MockCreator.java:50)
If I uncomment #PowerMockRunnerDelegate(SpringRunner.class) there it comes this other error:
Exception in thread "main" java.lang.NoClassDefFoundError: org/powermock/core/testlisteners/GlobalNotificationBuildSupport$Callback
at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner.run(DelegatingPowerMockRunner.java:139)
In the when method, try using any(HttpServletRequest.class) instead of the request mock object. Also use MockHttpServletRequest instead of mocking HttpServletRequest. This should work,
#RunWith(PowerMockRunner.class)
#PrepareForTest(JwtUtils.class)
#PowerMockIgnore( {"javax.management.*"})
public class RestAccessIntegrationTest {
#InjectMocks
private RestController restController;
private MockHttpServletRequest request;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(
new ServletRequestAttributes(request));
}
#Test
public void operationsPerAccountWhenSuccessfulTest() {
mockStatic(JwtUtils.class);
when(JwtUtils.myMethod(any(HttpServletRequest.class)))
.thenReturn("blabla");
String expected = ... ;
// does your getOperations take HttpServletRequest
// as parameter, then controller.getOperations(request);
String actual = restController.getOperations();
assertEquals(actual, expected);
}
}
It was due to incompatibility in library version of PowerMock and Mockito. I suggest to check the compatibility version table provided by PowerMock team or to switch to JMockit to mock static and private methods.
I have a method which calls async function:
public class MyService {
...
public void uploadData() {
MyPool.getInstance().getThreadPool().execute(new Runnable() {
#Override
public void run() {
boolean suc = upload();
}
});
}
}
I want to unit test this function with Mockito, I tried:
MyPool mockMyPool = Mockito.mock(MyPool.class);
ThreadPool mockThreadPool = Mockito.mock(ThreadPool.class);
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
when(mockMyPool.getThreadPool()).thenReturn(mockThreadPool);
MyService service = new MyService();
// run the method under test
service.uploadData();
// set the runnableCaptor to hold your callback
verify(mockThreadPool).execute(runnableCaptor.capture());
But I got error:
org.mockito.exceptions.verification.WantedButNotInvoked:
Wanted but not invoked:
threadPool.execute(
<Capturing argument>
);
Why I got this error, how to unit test uploadData() function with Mockito?
OK, I figured out a way by myself, since MyPool is an singleton. I added one public function setInstance(mockedInstance) to pass the mocked instance to MyPool. Then, it works. I know it is a bit "dirty", but if you have better solution, please let me know. Thanks!
Aside from the DI approach you found of keeping a MyPool or ThreadPool field, you can also refactor a little bit to allow for dependency injection in your method:
public class MyService {
...
public void uploadData() {
uploadData(MyPool.getInstance().getThreadPool());
}
/** Receives an Executor for execution. Package-private for testing. */
void uploadData(Executor executor) {
executor.execute(new Runnable() {
#Override public void run() {
boolean suc = upload();
}
});
}
}
This might be even cleaner, because it reduces your ThreadPool to the level of abstraction you need (Executor), which means you're only mocking a one-method interface rather than your ThreadPool (which I assume is related to ThreadPoolService; otherwise, you can just accept a ThreadPool, too). Officially your uploadData() would be untested, but you could easily and thoroughly test uploadData(Executor) or uploadData(ThreadPool), which are the moving parts most likely to break.
The package-private trick does rely on your code and tests to be in the same package, though they could be in different source folders; alternatively, you could just make the ThreadPool-receiving call a part of your public API, which would allow for more flexibility later.