23

I have a Spring Boot application, and I am trying to use @Autowired in a JUnit 5 extension. However, I cannot get it to work. (The @Autowired field is null.) Can anybody help?

Below is code that demonstrates the problem I'm having (the important parts are SomeExtension and SomeTest. As written, mvn test causes the test to fail in beforeEach. Sorry if I'm including too much.

src/test/java/somepackage/SomeExtension.java:

package somepackage;

import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.junit.jupiter.api.Assertions.assertNotNull;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SomeExtension implements BeforeEachCallback {
    @Autowired
    SomeBean bean;

    @Override
    public void beforeEach(ExtensionContext context) {
        assertNotNull(bean);
    }
}

src/test/java/somepackage/SomeTest.java:

package somepackage;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;


@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(SomeExtension.class)
class SomeTest {
    @Test
    void nothingTest() {
    }
}

src/main/java/somepackage/SomeBean.java

package somepackage;

import org.springframework.stereotype.Component;

@Component
public class SomeBean {
}

src/main/java/somepackage/MainClass.java

package somepackage;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MainClass {
    public static void main(String[] args) {
        SpringApplication.run(MainClass.class, args);
    }
}

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>fooGroupId</groupId>
    <artifactId>barArtifactId</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <!-- Don't include Junit 4 -->
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.2.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <!--
                This is copied from https://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven
                This allows the surefire plugin to be able to find Junit 5 tests, so `mvn test` works.
                -->
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.21.0</version>
                <dependencies>
                    <dependency>
                        <groupId>org.junit.platform</groupId>
                        <artifactId>junit-platform-surefire-provider</artifactId>
                        <version>1.2.0</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>

</project>

I'm also having similar issues with @Value. If the solution also works for that, it would be great.

Thank you.

1 Answers1

4

JUnit 5 extensions can not operate on other extensions, just on test classes.

So...

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SomeExtension implements BeforeEachCallback {

    @Autowired
    SomeBean bean;

    @Override
    public void beforeEach(ExtensionContext context) {
        assertNotNull(bean);
    }

}

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(SomeExtension.class)
class SomeTest {

    @Test
    void nothingTest() {
    }

}

... can not work. This would:

public class SomeExtension implements BeforeEachCallback {

    @Override
    public void beforeEach(ExtensionContext context) {
        // [...]
    }

}

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(SomeExtension.class)
class SomeTest {

    @Autowired
    SomeBean bean;

    @Test
    void nothingTest() {
    }

}

If you can explain why you need a bean in your extension, we may be able to help you find a fix for that, too.

Nicolai Parlog
  • 47,972
  • 24
  • 125
  • 255
  • 2
    I'm trying to add a precondition and postcondition that must be satisfied in every test in every class I specify. My approach was to have an extension implement BeforeEachCallback and AfterEachCallback. Checking the precondition and postcondition requires access to some beans. – Theemathas Chirananthavat Aug 06 '18 at 21:09
  • @TheemathasChirananthavat: Can you edit your question to clarify that use case? While you're at it, you can remove a few things, to make it shorter: all imports and package declarations as well as `SomeBean`, `MainClass` and the POM - they are not needed to answer your question. – Nicolai Parlog Aug 07 '18 at 07:34
  • 2
    Actually, JUnit Jupiter extensions _can_ possibly interact with other extensions if those other extensions are registered programmatically via `@RegisterExtension`. See my answer to https://stackoverflow.com/a/50251190/388980. ;-) – Sam Brannen Aug 08 '18 at 12:47
  • 20
    Why not just use `SpringExtension.getApplicationContext().getBean(SomeBean.class)` in this case? – Torsten Oct 21 '18 at 18:25
  • @Torsten +1 Used that approach and it solved exactly my issue. – Fredrik Corneliusson May 22 '19 at 14:28
  • @Torsten `getApplicationContext` takes `ExtensionContext` as an argument. What should I pass as `ExtensionContext`? – Harold L. Brown Apr 08 '20 at 07:13
  • @HaroldL.Brown the extensionContext that is passed as argument inside your own extension method, e.g. `SomeExtension::beforeEach` in this answer code. You can't do that in the Test class directly – kscherrer Aug 12 '20 at 06:38
  • Amazing how complicated it is to get Autowired annotations working in tests. In Junit 4 it worked out of the box – Marian Klühspies Nov 19 '20 at 15:29
  • @MarianKlühspies Didn't you need a dedicated runner or rule for that? – Nicolai Parlog Nov 23 '20 at 12:48
  • @NicolaiParlog Not sure about that, but it seems that when using SpringBootTest without ExtendsWith the Autowired annotations works as expected – Marian Klühspies Nov 24 '20 at 11:04
  • @MarianKlühspies You may confusing this scenario with the one where you want to inject into a test. That's not what the question (or answer) is about - it's about injecting into an extension. And I don't think that would have been possible in JUnit 4 either. Surely not in a runner (_Highlander rule_) and I suspect neither in a rule. – Nicolai Parlog Nov 24 '20 at 14:07
  • @Torsten you should add that as an answer. – VaibS Jul 30 '21 at 15:26