Reputation: 4409
I'm trying to write an integration test for a Spring Boot application (written in Kotlin but should be similar in Java). It's a codebase I've inherited so I'm not too familiar with Spring Boot, but I've managed to get a setup in place to actually run tests.
One problem I have is that a test that sends a HTTP request that requires authorization always fails, returning 403 Forbidden
. I think I've setup Spring Security to mock my principal because I can breakpoint into my security context factory and it does find my mock user fine, but it seems like the security context is never used in the web request.
Here is my test class:
class PlanIntegrationTests : BaseIntegrationTests() {
@Autowired
private lateinit var userRepository: DatabaseUserRecordRepository
@Autowired
private lateinit var planRepository: DatabasePlanRepository
private lateinit var payPerTestPlan: Plan
private lateinit var payPerMonthPlan: Plan
@BeforeEach
fun setup() {
cleanDatabase()
// Setup initial data into my test database - this all works fine
val userSeeds = UserSeeds(userRepository)
val planSeeds = PlanSeeds(planRepository)
runBlocking {
val adminUser = userSeeds.insertAdmin()
payPerTestPlan = planSeeds.insertPayPerTestPlan()
payPerMonthPlan = planSeeds.insertPayPerMonthPlan()
}
}
// This test always fails, why?
@Test
@WithLocalUser(username = UserSeeds.ADMIN_EMAIL)
fun `should return pay per test plan by id`(@Autowired webClient: WebTestClient) {
webClient
.get().uri("/plans/$payPerTestPlan.id")
.exchange()
.expectStatus().isOk()
.expectBody<Plan>()
.consumeWith {
assertEquals(it.responseBody?.id, payPerTestPlan.id)
assertEquals(it.responseBody?.name, payPerTestPlan.name)
}
}
}
The base class has the annotations I think are required:
@ContextConfiguration(initializers = [DockerComposeInitializer::class])
@SpringBootTest()
@AutoConfigureWebTestClient
class BaseIntegrationTests { }
I tried @WithMockUser
and @WithUserDetails
but these didn't seem to work, possibly because the web application doesn't use the UserDetailsService
directly. So I made my own annotation and SecurityContextFactory
:
@Retention(AnnotationRetention.RUNTIME)
@WithSecurityContext(factory = IntegrationTestsSecurityContextFactory::class, setupBefore = TestExecutionEvent.TEST_METHOD)
annotation class WithLocalUser(val username: String)
class IntegrationTestsSecurityContextFactory(
// LocalUserDetailsService is from the web application and does the user lookup
@Autowired private val localUserDetailsService: LocalUserDetailsService
) : WithSecurityContextFactory<WithLocalUser> {
override fun createSecurityContext(withUser: WithLocalUser): SecurityContext {
val context = SecurityContextHolder.createEmptyContext()
// This code works fine - it locates my test user and sets up the principal fine (I think)
val principal = runBlocking {
val userDetails = localUserDetailsService.findByUsername(withUser.username)
userDetails.awaitSingleOrNull()
}
context.authentication = UsernamePasswordAuthenticationToken(principal, principal?.password, principal?.authorities)
return context
}
}
To me, everything looks like it is set up right, but clearly it isn't because it doesn't work. Am I missing some Spring annotations somewhere, or autowiring the wrong services?
For further context, I'm using spring-boot-starter-webflux
.
Upvotes: 0
Views: 57