Possibly related to https://github.com/spring-projects/spring-boot/issues/39270, there is also an issue when a test in a class is disabled. Example:
package com.example.demo;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
//@ExtendWith(MockitoExtension.class)
@ExtendWith(SpringExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MultipleTestsNotNestedTests {
private static final UUID uuid = UUID.randomUUID();
@Mock
private MockedStatic<UUID> mockedStatic;
@Test
@Order(1)
@Disabled
void shouldReturnConstantValueDisabled() {
mockedStatic.when(UUID::randomUUID).thenReturn(uuid);
UUID result = UUID.randomUUID();
assertThat(result).isEqualTo(uuid);
}
@Test
@Order(2)
void shouldReturnConstantValue() {
mockedStatic.when(UUID::randomUUID).thenReturn(uuid);
UUID result = UUID.randomUUID();
assertThat(result).isEqualTo(uuid);
}
}
Order is important, the @Disabled test must run (or be skipped) before, not after the other test. When preparing the test instance the second time, the MockedStatic from the first run has not been closed, and the test fails with:
org.mockito.exceptions.base.MockitoException:
For java.util.UUID, static mocking is already registered in the current thread
To create a new mock, the existing static mock registration must be deregistered
Removing the @Disabled annotation results in the test passing. Again, using the MockitoExtension instead of the SpringExtension also results in the test working as expected with the @Disabled test.
Comment From: mhalbritter
org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener#prepareTestInstance is called, which initializes the mocks. But nothing is called afterwards to close them again if a test is disabled. On non-disabled tests, afterTestMethod does the cleanup.
Comment From: bernie-schelberg-invicara
@mhalbritter Thanks for your prompt attention to this issue. I have a concern that the applied fix overlooks another, more obscure corner case. If a test class run with SpringExtension containing an @Disabled test runs before another test class run with MockitoExtension, the same error will again occur. I've rebuilt the latest spring-boot code containing the fix and verified this to be the case. It's not so easy to demonstrate, as it involves multiple files.
The JUnit MockitoExtension doesn't open mocks until the "before-each" phase, thus avoiding this issue. I speculate that the Spring Boot approach of opening mocks in the "prepare" phase is due to them potentially being required to inject into Bean fields. I wonder if this design decision is absolute, or perhaps could be re-evaluated? I'm happy to dig into it some more myself, but feel like a fix for this would require more knowledge of the Spring Boot Test architecture than I currently possess.
Comment From: mhalbritter
Hey @bernie-schelberg-invicara, thanks for testing my changes. Can you provide me your reproducer which fails with the current changes?
Comment From: bernie-schelberg-invicara
@mhalbritter Sure, add this line to the Test configuration in your build.gradle.kts or equivalent:
systemProperty("junit.jupiter.testclass.order.default", "org.junit.jupiter.api.ClassOrderer\$OrderAnnotation")
Then unzip the attached files in the appropriate place and run them both.
I would give you my full project, but the Gradle file is a bit of a mess, to use the snapshot version of the plugin. Is there any easy way to compile and use the plugin and all it's dependencies without publishing to a Maven repo?
Comment From: mhalbritter
Thank you! I've added more tests and have hopefully fixed all cases where mocks haven't been closed.