Created
October 20, 2020 01:28
-
-
Save mike-neck/86fc8d4dfe3ee2905f74fd2066575c1a to your computer and use it in GitHub Desktop.
Spring WebMvc のテスト
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | |
| plugins { | |
| id("org.springframework.boot") version "2.3.4.RELEASE" | |
| id("io.spring.dependency-management") version "1.0.10.RELEASE" | |
| kotlin("jvm") version "1.3.72" | |
| kotlin("plugin.spring") version "1.3.72" | |
| } | |
| group = "com.example" | |
| version = "0.0.1-SNAPSHOT" | |
| java.sourceCompatibility = JavaVersion.VERSION_11 | |
| repositories { | |
| mavenCentral() | |
| } | |
| dependencies { | |
| implementation("org.springframework.boot:spring-boot-starter-security") | |
| implementation("org.springframework.boot:spring-boot-starter-oauth2-client") | |
| implementation("org.springframework.boot:spring-boot-starter-web") | |
| implementation("com.fasterxml.jackson.module:jackson-module-kotlin") | |
| implementation("org.jetbrains.kotlin:kotlin-reflect") | |
| implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") | |
| testImplementation("org.springframework.boot:spring-boot-starter-test") { | |
| exclude(group = "org.junit.vintage", module = "junit-vintage-engine") | |
| } | |
| testImplementation("org.springframework.security:spring-security-test") | |
| } | |
| tasks.withType<Test> { | |
| useJUnitPlatform() | |
| } | |
| tasks.withType<KotlinCompile> { | |
| kotlinOptions { | |
| freeCompilerArgs = listOf("-Xjsr305=strict") | |
| jvmTarget = "11" | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.example | |
| import org.slf4j.Logger | |
| import org.slf4j.LoggerFactory | |
| import org.springframework.boot.autoconfigure.SpringBootApplication | |
| import org.springframework.boot.runApplication | |
| import org.springframework.context.annotation.Bean | |
| import org.springframework.context.annotation.Configuration | |
| import org.springframework.core.annotation.Order | |
| import org.springframework.security.config.annotation.web.builders.HttpSecurity | |
| import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter | |
| import org.springframework.security.core.annotation.AuthenticationPrincipal | |
| import org.springframework.security.oauth2.core.user.OAuth2User | |
| import org.springframework.stereotype.Component | |
| import org.springframework.web.bind.annotation.GetMapping | |
| import org.springframework.web.bind.annotation.RequestMapping | |
| import org.springframework.web.bind.annotation.RequestParam | |
| import org.springframework.web.bind.annotation.RestController | |
| import kotlin.IllegalStateException | |
| import java.time.Clock | |
| import java.time.Instant | |
| import java.time.OffsetDateTime | |
| import java.time.ZoneId | |
| import java.time.ZoneOffset | |
| import java.time.format.DateTimeFormatter | |
| import java.time.format.TextStyle | |
| import java.util.* | |
| import kotlin.reflect.KClass | |
| @SpringBootApplication | |
| class ControllerTestWithSecurityApplication { | |
| @Bean | |
| fun clock(): Clock = Clock.systemUTC() | |
| @Bean | |
| fun defaultTimeZone(): DefaultTimeZone = DefaultTimeZone(ZoneId.of("UTC").getDisplayName(TextStyle.SHORT, Locale.US)) | |
| } | |
| @Configuration | |
| class SecurityConfig: WebSecurityConfigurerAdapter() { | |
| override fun configure(http: HttpSecurity?): Unit = | |
| if (http == null) throw IllegalStateException("http security should be not null") | |
| else http.authorizeRequests { it.anyRequest().authenticated() } | |
| .oauth2Login() | |
| .let { Unit } | |
| } | |
| fun main(args: Array<String>) { | |
| runApplication<ControllerTestWithSecurityApplication>(*args) | |
| } | |
| class DefaultTimeZone(val value: String) { | |
| fun offset(instant: Instant): ZoneOffset = ZoneId.of(value).rules.getOffset(instant) | |
| } | |
| data class CurrentTime(val now: Long, val nowStr: String, val iso: String, val timeZone: String) { | |
| constructor(offsetDateTime: OffsetDateTime, timeZone: String): | |
| this( | |
| offsetDateTime.toInstant().toEpochMilli(), | |
| offsetDateTime.toInstant().toEpochMilli().toString(), | |
| offsetDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), | |
| timeZone) | |
| constructor(instant: Instant, defaultTimeZone: DefaultTimeZone): | |
| this(instant.atOffset(defaultTimeZone.offset(instant)), defaultTimeZone.value) | |
| constructor(clock: Clock, defaultTimeZone: DefaultTimeZone): this(Instant.now(clock), defaultTimeZone) | |
| } | |
| inline fun <reified T: Any> logger(klass: KClass<T> = T::class): Logger = LoggerFactory.getLogger(klass.java) | |
| interface TimeService { | |
| fun currentTime(timeZone: String?): CurrentTime | |
| } | |
| @Component | |
| @Order(10) | |
| class TimeServiceImpl( | |
| private val clock: Clock, | |
| private val defaultTimeZone: DefaultTimeZone): TimeService { | |
| override fun currentTime(timeZone: String?): CurrentTime = | |
| if (timeZone.isNullOrEmpty()) | |
| CurrentTime(clock, defaultTimeZone) | |
| else CurrentTime(clock, DefaultTimeZone(timeZone)) | |
| } | |
| @RestController | |
| @RequestMapping("time") | |
| class TimeController( | |
| private val timeService: TimeService) { | |
| @GetMapping(produces = ["application/vnd.example.v3+json"]) | |
| fun get(@RequestParam timeZone: String?, @AuthenticationPrincipal user: OAuth2User): CurrentTime = | |
| logger.info("request = timeZone: {}, user: {}, {}", timeZone, user.authorities, user.attributes).let { | |
| timeService.currentTime(timeZone) | |
| } | |
| companion object { | |
| @JvmStatic | |
| private val logger = logger<TimeController>() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.example | |
| import org.junit.jupiter.api.Test | |
| import org.mockito.Mockito.`when` | |
| import org.mockito.stubbing.OngoingStubbing | |
| import org.springframework.beans.factory.annotation.Autowired | |
| import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc | |
| import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest | |
| import org.springframework.boot.test.context.SpringBootTest | |
| import org.springframework.boot.test.mock.mockito.MockBean | |
| import org.springframework.mock.web.MockHttpSession | |
| import org.springframework.security.core.context.SecurityContextImpl | |
| import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken | |
| import org.springframework.security.oauth2.core.user.DefaultOAuth2User | |
| import org.springframework.security.oauth2.core.user.OAuth2UserAuthority | |
| import org.springframework.security.web.context.HttpSessionSecurityContextRepository | |
| import org.springframework.test.web.servlet.MockMvc | |
| import org.springframework.test.web.servlet.request.MockMvcRequestBuilders | |
| import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath | |
| import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status | |
| import java.time.OffsetDateTime | |
| import java.time.ZoneOffset | |
| @SpringBootTest( | |
| webEnvironment = SpringBootTest.WebEnvironment.MOCK, | |
| classes = [ControllerTestWithSecurityApplication::class] | |
| ) | |
| @AutoConfigureMockMvc | |
| class MockMvcTests { | |
| @Autowired | |
| lateinit var mvc: MockMvc | |
| @Test | |
| fun get() { | |
| mvc.perform(MockMvcRequestBuilders.get("/time").session(Session)) | |
| .andExpect(status().isOk) | |
| .andExpect(jsonPath("$.timeZone").value("UTC")) | |
| .andExpect(jsonPath("$.now").isNumber) | |
| .andExpect(jsonPath("$.nowStr").isString) | |
| .andExpect(jsonPath("$.iso").isString) | |
| } | |
| } | |
| object Session: MockHttpSession() { | |
| init { | |
| setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextImpl(principal())) | |
| } | |
| private inline fun <A: Any, B: Any> A.withMapping(f: (A) -> B): Pair<A, B> = this to f(this) | |
| private fun principal(): OAuth2AuthenticationToken = | |
| mapOf("sub" to "00aa11bb22cc", "email" to "user@example.com") | |
| .withMapping { listOf(OAuth2UserAuthority("ROLE_USER", it)) } | |
| .withMapping { DefaultOAuth2User(it.second, it.first, "sub") } | |
| .let { OAuth2AuthenticationToken(it.second, it.first.second, "0011-client-id") } | |
| } | |
| @WebMvcTest(TimeController::class) | |
| class WebMvcMockServiceTests { | |
| @Autowired | |
| lateinit var mvc: MockMvc | |
| @MockBean | |
| lateinit var service: TimeService | |
| @Test | |
| fun get() { | |
| calling(service.currentTime(null)).thenReturn(CurrentTime(fixedTime, "UTC")) | |
| mvc.perform(MockMvcRequestBuilders.get("/time").session(Session)) | |
| .andExpect(status().isOk) | |
| .andExpect(jsonPath("$.timeZone").value("UTC")) | |
| .andExpect(jsonPath("$.now").value(fixedTime.toInstant().toEpochMilli())) | |
| .andExpect(jsonPath("$.nowStr").isString) | |
| .andExpect(jsonPath("$.iso").isString) | |
| } | |
| companion object { | |
| val fixedTime: OffsetDateTime = OffsetDateTime.of(2020, 1, 2, 15, 4, 5, 600_000_000, ZoneOffset.ofHours(9)) | |
| fun <T> calling(any: T): OngoingStubbing<T> = `when`(any) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment