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.
Spring Webflux does not block a thread to handle each request, because no thread is kept waiting for something to be done (e.g. waiting for an answer from a database). As written in 1., it can be blocked while waiting for an answer from a database or from another service that is called via HTTP.
Introduction : While working with Spring MVC, you might have used Spring MockMVC to perform testing of Spring web MVC controllers. MockMVC class is part of the Spring MVC test framework which helps in testing the controllers explicitly starting a Servlet container.
Spring WebFlux makes it possible to build reactive applications on the HTTP layer. It is a reactive fully non-blocking, annotation-based web framework built on Project Reactor that supports reactive streams back pressure and runs on non-blocking servers such as Netty, Undertow and Servlet 3.1+ containers.
By default, tests annotated with @WebFluxTest will also auto-configure a WebTestClient. Typically @WebFluxTest is used in combination with @MockBean or @Import to create any collaborators required by the @Controller beans.
We can, however, opt to handle our WebFlux errors at a global level. To do this, we only need to take two steps: The exception that our handler throws will be automatically translated to an HTTP status and a JSON error body.
The Maven setup is the same as our previous article, which provides an introduction to Spring Webflux. For our example, we'll use a RESTful endpoint that takes a username as a query parameter and returns “Hello username” as a result.
Throwing an Exception Any time we throw an exception, the default HTTP return status is omitted and Spring tries to find an exception handler to deal with it: To learn more about how to do that, definitely check-out the Error Handling article on Baeldung. 2.5.
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With