Reputation: 1981
Great thx for viewing my question) I have some strange subject: my spring boot tests don't work. They start successfully but always throwing 403 HTTP status when making the requests to any controller I have some project with next dependencies:
buildscript {
ext.kotlin_version = '1.3.71'
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.71"
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.2.1.RELEASE"
classpath "com.google.cloud.tools.jib:com.google.cloud.tools.jib.gradle.plugin:1.8.0"
}
}
plugins {
id "org.springframework.boot" version "2.2.5.RELEASE"
id "io.spring.dependency-management" version "1.0.9.RELEASE"
id "com.google.cloud.tools.jib" version "1.8.0"
id "org.jetbrains.kotlin.jvm" version "1.3.71"
id "org.jetbrains.kotlin.plugin.spring" version "1.3.71"
id "org.jetbrains.kotlin.plugin.jpa" version "1.3.71"
}
apply plugin: 'kotlin'
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
}
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
maven {
url "http://oss.jfrog.org/artifactory/oss-snapshot-local/"
}
mavenCentral()
}
kotlin {
sourceSets {
main.kotlin.srcDirs += 'src/main/myKotlin'
}
}
dependencies {
implementation "joda-time:joda-time:2.10.5"
implementation "org.springframework.boot:spring-boot-starter-security"
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "org.springframework.boot:spring-boot-starter-batch"
implementation "org.springframework.boot:spring-boot-starter-jdbc"
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
implementation "org.springframework.boot:spring-boot-starter-data-mongodb-reactive"
implementation "org.springframework.boot:spring-boot-starter-mail"
implementation "org.springframework.boot:spring-boot-starter-quartz"
implementation "org.springframework.boot:spring-boot-starter-webflux"
implementation "com.fasterxml.jackson.module:jackson-module-kotlin"
implementation "io.springfox:springfox-swagger2:3.0.0-SNAPSHOT"
implementation "io.springfox:springfox-swagger-ui:3.0.0-SNAPSHOT"
implementation "io.springfox:springfox-spring-webflux:3.0.0-SNAPSHOT"
implementation "org.flywaydb:flyway-core"
implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactor"
developmentOnly "org.springframework.boot:spring-boot-devtools"
runtimeOnly "org.postgresql:postgresql"
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude group: "org.junit.vintage", module: "junit-vintage-engine"
}
testImplementation("de.flapdoodle.embed:de.flapdoodle.embed.mongo")
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.springframework.batch:spring-batch-test")
testImplementation("com.ninja-squad:springmockk:2.0.0")
testImplementation('com.h2database:h2')
}
test {
useJUnitPlatform()
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
I implemented some functionality, and then I began coverage it with tests. But, then I start them, spring always throws exception with 403 response status. Here is the reports:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.5.RELEASE)
2020-03-26 11:24:23.345 INFO 38406 --- [ Test worker] r.m.b.web.signin.SignInControllerTest : Starting SignInControllerTest on MacBook-Pro-Apple with PID 38406 (started by alexscrobot in /Users/alexscrobot/development/backends/medissima)
2020-03-26 11:24:23.347 INFO 38406 --- [ Test worker] r.m.b.web.signin.SignInControllerTest : No active profile set, falling back to default profiles: default
2020-03-26 11:24:26.418 INFO 38406 --- [ Test worker] ctiveUserDetailsServiceAutoConfiguration :
Using generated security password: 499ec504-7b25-484f-87b3-d6b6d2f7cc06
2020-03-26 11:24:26.553 DEBUG 38406 --- [ Test worker] s.w.r.r.m.a.RequestMappingHandlerMapping : 1 mappings in 'requestMappingHandlerMapping'
2020-03-26 11:24:26.671 DEBUG 38406 --- [ Test worker] o.s.w.r.handler.SimpleUrlHandlerMapping : Patterns [/swagger-ui.html**, /webjars/**] in 'resourceHandlerMapping'
2020-03-26 11:24:26.765 DEBUG 38406 --- [ Test worker] o.s.w.r.r.m.a.ControllerMethodResolver : ControllerAdvice beans: none
2020-03-26 11:24:27.338 DEBUG 38406 --- [ Test worker] o.s.w.s.adapter.HttpWebHandlerAdapter : enableLoggingRequestDetails='false': form data and headers will be masked to prevent unsafe logging of potentially sensitive data
2020-03-26 11:24:27.390 INFO 38406 --- [ Test worker] r.m.b.web.signin.SignInControllerTest : Started SignInControllerTest in 4.384 seconds (JVM running for 6.038)
2020-03-26 11:24:28.628 DEBUG 38406 --- [ Test worker] o.s.w.r.f.client.ExchangeFunctions : [48fcb68c] HTTP POST /api/sign-in
2020-03-26 11:24:28.759 DEBUG 38406 --- [ parallel-1] o.s.http.codec.json.Jackson2JsonEncoder : [48fcb68c] Encoding [RequestCredentials([email protected], password=123456)]
2020-03-26 11:24:28.797 DEBUG 38406 --- [ parallel-1] o.s.w.s.adapter.HttpWebHandlerAdapter : [6997f224] HTTP POST "/api/sign-in"
2020-03-26 11:24:28.911 DEBUG 38406 --- [oundedElastic-1] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession.
2020-03-26 11:24:28.926 DEBUG 38406 --- [oundedElastic-1] o.s.w.s.adapter.HttpWebHandlerAdapter : [6997f224] Completed 403 FORBIDDEN
2020-03-26 11:24:28.930 DEBUG 38406 --- [oundedElastic-1] o.s.w.r.f.client.ExchangeFunctions : [48fcb68c] Response 403 FORBIDDEN
Status expected:<200 OK> but was:<403 FORBIDDEN>
> POST /api/sign-in
> WebTestClient-Request-Id: [1]
> Accept: [application/json]
> Content-Type: [application/json]
> Content-Length: [45]
{"email":"[email protected]","password":"123456"}
< 403 FORBIDDEN Forbidden
< Content-Type: [text/plain]
< Cache-Control: [no-cache, no-store, max-age=0, must-revalidate]
< Pragma: [no-cache]
< Expires: [0]
< X-Content-Type-Options: [nosniff]
< X-Frame-Options: [DENY]
< X-XSS-Protection: [1 ; mode=block]
< Referrer-Policy: [no-referrer]
CSRF Token has been associated to this client
java.lang.AssertionError: Status expected:<200 OK> but was:<403 FORBIDDEN>
> POST /api/sign-in
> WebTestClient-Request-Id: [1]
> Accept: [application/json]
> Content-Type: [application/json]
> Content-Length: [45]
{"email":"[email protected]","password":"123456"}
< 403 FORBIDDEN Forbidden
< Content-Type: [text/plain]
< Cache-Control: [no-cache, no-store, max-age=0, must-revalidate]
< Pragma: [no-cache]
< Expires: [0]
< X-Content-Type-Options: [nosniff]
< X-Frame-Options: [DENY]
< X-XSS-Protection: [1 ; mode=block]
< Referrer-Policy: [no-referrer]
CSRF Token has been associated to this client
at org.springframework.test.web.reactive.server.ExchangeResult.assertWithDiagnostics(ExchangeResult.java:209)
at org.springframework.test.web.reactive.server.StatusAssertions.assertStatusAndReturn(StatusAssertions.java:227)
at org.springframework.test.web.reactive.server.StatusAssertions.isOk(StatusAssertions.java:67)
at ru.medissima.backend.web.signin.SignInControllerTest.success authentication(SignInControllerTest.kt:80)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.stop(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:132)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:412)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.AssertionError: Status expected:<200 OK> but was:<403 FORBIDDEN>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
at org.springframework.test.web.reactive.server.StatusAssertions.lambda$assertStatusAndReturn$4(StatusAssertions.java:227)
at org.springframework.test.web.reactive.server.ExchangeResult.assertWithDiagnostics(ExchangeResult.java:206)
... 91 more
2020-03-26 11:24:28.990 DEBUG 38406 --- [ Test worker] o.s.w.r.f.client.ExchangeFunctions : [5ee937d3] HTTP POST /api/sign-in
2020-03-26 11:24:28.991 DEBUG 38406 --- [ parallel-2] o.s.w.s.adapter.HttpWebHandlerAdapter : [5fe66132] HTTP POST "/api/sign-in"
2020-03-26 11:24:28.992 DEBUG 38406 --- [oundedElastic-1] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession.
2020-03-26 11:24:28.994 DEBUG 38406 --- [oundedElastic-1] o.s.w.s.adapter.HttpWebHandlerAdapter : [5fe66132] Completed 403 FORBIDDEN
2020-03-26 11:24:28.994 DEBUG 38406 --- [oundedElastic-1] o.s.w.r.f.client.ExchangeFunctions : [5ee937d3] Response 403 FORBIDDEN
SignInControllerTest > success authentication() FAILED
java.lang.AssertionError at SignInControllerTest.kt:80
Caused by: java.lang.AssertionError at SignInControllerTest.kt:80
2 tests completed, 1 failed
Here is the code of test implementation
@RunWith(value = SpringRunner::class)
@WebFluxTest(value = [SignInController::class])
class SignInControllerTest(
@Autowired val webTestClient: WebTestClient
) {
@MockkBean
private lateinit var service: AuthorizationService
@MockkBean
private lateinit var validationService: ValidationService
@MockkBean
private lateinit var repository: AuthRepository
@Test
fun `failed sign in test`() {
webTestClient
.post()
.uri("/api/sign-in")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isForbidden
}
@Test
fun `success authentication`() {
val user = createTestUser()
val request = RequestCredentials(
"[email protected]",
"123456"
)
val response = Mono.just(user)
every { repository.findByEmail("") }.returns(Optional.of(user))
every { validationService.matchPasswords("", "") }.returns(Unit)
every { service.authorize("", "") } returns response
webTestClient
.post()
.uri("/api/sign-in")
.bodyValue(request)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk
}
}
I saw, that csrf token required, but i tried to tuned configuration like this:
@Configuration
class SecurityConfiguration {
@Bean
fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/api/**").permitAll()
.anyExchange().permitAll()
.and()
.httpBasic()
.and()
.build()
}
}
It's didn't work. Please, help me to understand why test environment not started correctly.
Upvotes: 9
Views: 7700
Reputation: 4769
This situation gave me a tough time as well. So the summary of the issue is that the tests are not able to understand the Security config defined for your application. Security config needs to be imported in the test for working.
MySecurityConfig in application defined as a bean
@Configuration
@EnableWebFluxSecurity
public class MySecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(final ServerHttpSecurity http) {
http.oauth2Client();
return http.authorizeExchange().anyExchange().permitAll().and().csrf().disable().build();
}
}
My Flux controller is a simple controller, which talks to a service which returns Mono and controller maps that Mono to a wrapper response object.
Below two lines goes on your test class. So this will launch WebFluxTest with the dependencies required for YourController And we are also importing the security config defined in MySecurityConfig
@WebFluxTest(value = YourController.class)
@Import(MySecurityConfig.class)
Here is snippet of the test class
@WebFluxTest(value = YourController.class)
@Import(MySecurityConfig.class)
class YourControllerUnitTest {
@MockBean //mock bean will be inject here
private YourService yourService ;
@Autowired
private WebTestClient webTestClient;
@SneakyThrows
@DisplayName("get success response")
@Test
void getSuccessResponse() {
when(yourService.getData(any(YourRequestObject.class))).thenReturn(Mono.just(someMockResponseHere));
webTestClient.post().uri("/getData")
.bodyValue(requestJSON)
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$").isNotEmpty()
.jsonPath("$.result[0].name").isEqualTo("test")
this way the WebFlux controller can be tested in Isolation by mocking the downstream communication. You are still launching the context and accessing the API exposed by WebFlux Controller.
Upvotes: 0
Reputation: 1551
It's because you are making a POST request for which CSRF Protection is enabled. This is also the case for PUT requests. There's an open issue regarding this behaviour. For now, as a work-around, just modify your test code as shown below to get past the 403 response
webClient.mutateWith(csrf()).post()
Upvotes: 6