67

I Have the following abstract unit test class that all my concrete unit test classes extend:

@ExtendWith(SpringExtension.class)
//@ExtendWith(MockitoExtension.class)
@SpringBootTest(
    classes = PokerApplication.class,
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
public abstract class AbstractUnitTests {

    @MockBean
    public RoundService roundService;

    @MockBean
    public RoundRepository roundRepository;
}

What is the difference between using @ExtendWith(SpringExtension.class) or @ExtendWith(MockitoExtension.class)?

I ask as using either of the annotations seems to make no difference and both work respectively in my code - allowing me to use Junit5. So why do both work?

Concrete test class:

    @DisplayName("Test RoundService")
    public class RoundsServiceTest extends AbstractUnitTests {

        private static String STUB_USER_ID = "user3";

        // class under test
        @InjectMocks
        RoundService roundService;

        private Round round;

        private ObjectId objectId;

        @BeforeEach //note this replaces the junit 4 @Before
        public void setUp() {

            initMocks(this);
            round = Mocks.round();
            objectId = Mocks.objectId();
        }

        @DisplayName("Test RoundService.getAllRoundsByUserId()")
        @Test
        public void shouldGetRoundsByUserId() {

            // setup
            given(roundRepository.findByUserId(anyString())).willReturn(Collections.singletonList(round));

            // call method under test
            List<Round> rounds = roundService.getRoundsByUserId(STUB_USER_ID);

            // asserts
            assertNotNull(rounds);
            assertEquals(1, rounds.size());
            assertEquals("user3", rounds.get(0).userId());
        }
}

Relevant Build.gradle section :

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.2.2.RELEASE'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    implementation 'junit:junit:4.12'
}

test {
    useJUnitPlatform()
}
java12399900
  • 1,485
  • 7
  • 26
  • 56

3 Answers3

84

What is a Junit Extension

The purpose of Junit 5 extensions is to extend the behavior of test classes or methods

source

Read on Junit 5 Extension Model & @ExtendWith annotation :here

SpringExtension

SpringExtension integrates the Spring TestContext Framework into JUnit 5's Jupiter programming model.

public class SpringExtension
extends Object
implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor, BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback, ParameterResolver{..}

MockitoExtension

This extension is the JUnit Jupiter equivalent of our JUnit4 MockitoJUnitRunner

public class MockitoExtension
extends java.lang.Object
implements BeforeEachCallback, AfterEachCallback, ParameterResolver{..}

As it can be seen , SpringExtension implements a lot more extensions than MockitoExtension.

Also @SpringBootTest is meta annotated with @ExtendWith(SpringExtension.class) and which means every time your tests are extended with SpringExtension. @MockBean is a Spring test framework annotation and used along with @ExtendWith(SpringExtension.class)

To observe the difference try the following

ExtendWith only MockitoExtension

@ExtendWith(MockitoExtension.class)
class TestServiceTest {

    @MockBean
    TestService service;

    @Test
    void test() {
        assertNotNull(service); // Test will fail
    }

}

ExtendWith only SpringExtension

@ExtendWith(SpringExtension.class)
class TestServiceTest {

    @MockBean
    TestService service;

    @Test
    void test() {
        assertNotNull(service); // Test succeeds
    }

}

ExtendWith with both SpringExtension and MockitoExtension

@ExtendWith(MockitoExtension.class)
@ExtendWith(SpringExtension.class)
class TestServiceTest {

    @MockBean
    TestService service;

    @Test
    void test() {
        assertNotNull(service); // Test succeeds
    }

}

Both works in your case because of the @SpringBootTest annotation for the test class as explained.

To answer the question : When to use @ExtendWith Spring or Mockito? ,

When the test requires a Spring Test Context ( to autowire a bean / use of @MockBean ) along with JUnit 5's Jupiter programming model use @ExtendWith(SpringExtension.class). This will support Mockito annotations as well through TestExecutionListeners.

When the test uses Mockito and needs JUnit 5's Jupiter programming model support use @ExtendWith(MockitoExtension.class)

Hope this helps

R.G
  • 6,436
  • 3
  • 19
  • 28
16

When to use @ExtendWith(SpringExtension.class) or @SpringBootTest?

  • When you use Integration test -@SpringBootTest annotation- or any slice test -@xxxTest annotations- you don't need @ExtendWith(SpringExtension.class) annotation since mentioned annotations include it.

  • If you test @ConfigurationProperties, @Service, @Component annotated class ( not defined in slice test cases - ref:Spring Boot Reference Document Testing/Auto-configured / SLICED Tests item-, you may use @ExtendWith(SpringExtension.class) instead of @SpringBootTest.

Observation: I expect that a test with @ExtendWith(SpringExtension.class) faster than the same test with @SpringBootTest.When I perform a test in Eclipse i observed the reverse.

a.lgl
  • 161
  • 1
  • 3
  • 3
    In our case, we're using @SpringBootTest and passing in the specific configuration classes needed for the test. If we instead use only @ExtendWith(SpringExtension.class), it builds the entire application context, which of course takes longer. – Kyrstellaine Dec 15 '21 at 22:07
0

To add some extra information: I also recently figured out that if you use @Mock annotated dependencies in test class with MockitoExtension and you try to use Mockito.when(mockedDependency.methodName()) in @BeforeAll setup method, then you are getting an NullPointer on your mocked dependency.

But if you change MockitoExtension to SpringExtension, it works fine. Looks like with SpringExtension mocked beans is initialized earlier (before JUnit launches @BeforeAll method) just like it should properly work.

helvete
  • 2,455
  • 13
  • 33
  • 37
Excalibur
  • 1
  • 1