Search code examples
springspring-bootkotlincucumberjunit5

Spring Security context is always null in Spring Boot Cucumber Test


I have created a service with Spring Boot and Reactor. I try to test with Cucumber. POM file (extract):

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.2</version>
  </parent>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.projectreactor</groupId>
      <artifactId>reactor-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
    <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-java</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-spring</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-junit-platform-engine</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-suite</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-console</artifactId>
      <scope>test</scope>
    </dependency>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-bom</artifactId>
        <version>7.10.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.junit</groupId>
        <artifactId>junit-bom</artifactId>
        <version>5.9.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

My cucumber configuration looks like this:

@Suite
class CucumberTestRunner

@CucumberContextConfiguration
@ContextConfiguration(classes = [CucumberBootstrap::class])
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [MyApplication::class])
class CucumberSpringContextConfig

@ComponentScan("com.mypackage")
@Configuration
class CucumberBootstrap

The step definition I'm working on is like this:

@ScenarioScope
class MyTestStepDefs(
  private val service: MyService
) {

  @Given("User is authenticated")
  fun userIsAuthenticated() {
    val auth = UsernamePasswordAuthenticationToken(
      User("username", "password", emptyList()),
      "credentials",
      emptyList<GrantedAuthority>()
    )

    val context = ReactiveSecurityContextHolder.getContext().block()

    //ReactiveSecurityContextHolder.getContext()
    //  .doOnNext { it.authentication = auth }
    //  .block()
  }
...
}

I would like to set the authenticated user into the security context. But unfortunately, the security context is always null.

I have also regular Springt Boot Tests running, where the security context is correctly returned. Here is an extract:

@SpringBootTest(webEnvironment = RANDOM_PORT)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyControllerTests @Autowired constructor(
  private val client: WebTestClient
) {

  @Test
  @WithMockUser
  fun `post instruction set`() {
    val context = ReactiveSecurityContextHolder.getContext().block()
    ...
  }
  ...
}

The service class is injected correctly into the step definitions class.

Does anyone have a hint what's missing in the configuration?

Thanks in advance for your help!


Solution

  • Based on M.P. Korstanje's description, I implemented the step as follows:

      @Given("User is authenticated")
      fun userIsAuthenticated() {
        val auth = UsernamePasswordAuthenticationToken(
          User("username", "password", emptyList()),
          "credentials",
          emptyList<GrantedAuthority>()
        )
    
        val context = TestSecurityContextHolder.getContext()
        context.authentication = auth
    
        val newContext = ReactiveSecurityContextHolder.getContext().block()
        assertEquals(auth, newContext?.authentication, "Wrong authentication stored in security context")
      }