■
SpringBootで作ったWebアプリのユニットテスト
こちらを参考に作り始めたのですが、動かすまでは簡単にできたのですが、ユニットテストの方法が良くわからなかったので調べてみました。
あと、テストにはやっぱりSpockを使いたかったので、その辺も調べました。
Webアプリを作成
公式サイトのチュートリアル通りにやれば一直線です。迷うところは特にありません。 Spockも使うつもりだったので最初からGroovyで作っています。
HelloController.groovy
package la.urau import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMethod import org.springframework.web.bind.annotation.RestController @RestController class HelloController { @RequestMapping(value = "/hello", method = RequestMethod.GET, produces = ["application/json"]) String hello() { """{"result": true}""" } }
Application.groovy
package la.urau import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.Configuration @Configuration @ComponentScan @EnableAutoConfiguration class Application { public static void main(String[] args) { SpringApplication.run(Application, args) } }
ユニットテストを作成
JUnitでのテスト
たぶんこのSpringJUnit4ClassRunner
を利用するのが一番オーソドックスなやり方なんでしょうか。
HelloControllerTest.groovy
package la.urau import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.SpringApplicationConfiguration import org.springframework.http.MediaType import org.springframework.test.context.junit4.SpringJUnit4ClassRunner import org.springframework.test.context.web.WebAppConfiguration import org.springframework.test.web.servlet.MockMvc import org.springframework.web.context.WebApplicationContext import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.* import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.* import static org.springframework.test.web.servlet.setup.MockMvcBuilders.* @RunWith(SpringJUnit4ClassRunner) @SpringApplicationConfiguration(classes = Application) @WebAppConfiguration public class HelloControllerTest { @Autowired WebApplicationContext wac MockMvc mockMvc @Before void setup() { mockMvc = webAppContextSetup(wac).build() } @Test void "sample"() { mockMvc.perform(get("/hello")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath('$.result').value(true)) .andExpect(jsonPath('$.foo').doesNotExist()) } }
@RunWith(SpringJUnit4ClassRunner)
により、JUnit動作時にSpringのApplicationContext
なんかを生成してくれます。
次に@SpringApplicationConfiguration
で、Configuration
クラスを指定します(Application
クラスには、@CompnentScan
や@EnableAutoConfiguration
なんかが指定されているので、これがConfiguration
を兼ねている、、、たぶん)
最後に@WebAppConfiguration
を付けることで、WebApplicationContext
を利用することができ、Webアプリとしてのテストができる、というわけです。
Controller
に対するテストを行うために、MockMvc
が用意されています。MockMvc
のインスタンスをWebApplicationContext
を使って生成し、MockMvc#perform
でエンドポイントにアクセスすることで、結果を取得することが出来ます。
あとは結果の検証コードを書くだけで一応テストまで出来ました。
でも・・・
この方法は、SpringのApplicationContext
を生成してしまうため、テスト実行に少し時間がかかってしまいます。ユニットテストは何回も繰り返し実行されるものなので、塵も積もれば山となる感じで時間を浪費してしまいます。
そこで、テストコードを以下のように書き換えます。
public class HelloControllerTest { MockMvc mockMvc @Before void setup() { mockMvc = standaloneSetup(new HelloController()).build() } @Test void "sample"() { mockMvc.perform(get("/hello")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath('$.result').value(true)) .andExpect(jsonPath('$.foo').doesNotExist()) } }
動作的には変わりません。/hello
を呼び出してその結果を検証するユニットテストですが、不要なContextを生成しない分テスト実行が格段に早くなります(出力されるログの量が段違い・・・)
Spockでのテスト
以上がJUnitによるテストとなるわけですが、やっぱりSpockを使いたい!というわけで、以下は同等のテストをSpockで記述したものです。
class HelloControllerStandaloneSpec extends Specification { @Shared MockMvc mockMvc def setup() { mockMvc = standaloneSetup(new HelloController()).build() } def "sample"() { expect: mockMvc.perform(get("/hello")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath('$.result').value(true)) .andExpect(jsonPath('$.foo').doesNotExist()) } }
standaloneで動かす場合は特に注意する点はありません。普通にSpockの作法で記述していけば良いでしょう。
ではContextを使用したい場合どうするか?
これは少し追加が必要となります。
まずはbuild.gradle
に以下を追加。
testCompile 'org.spockframework:spock-spring'
その上で、Specクラスは以下のようになります。
@WebAppConfiguration @ContextConfiguration(loader = WebDelegatingSmartContextLoader, classes = Application) class HelloControllerIntegrationSpec extends Specification { @Autowired WebApplicationContext wac @Shared MockMvc mockMvc def setup() { mockMvc = webAppContextSetup(wac).build() } def "sample"() { expect: mockMvc.perform(get("/hello")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath('$.result').value(true)) .andExpect(jsonPath('$.foo').doesNotExist()) } }
注意すべきは、JUnitバージョンではあった、@RunWith(SpringJUnit4ClassRunner)
、@SpringApplicationConfiguration
がありません。
まずSpockは@RunWith(Sputnik.class)
が付いたSpecification
を継承してしまうため、@RunWith
を付けることができません。また、ここにあるとおり、@SpringApplicationConfiguration
は読んでくれないようです。
なので、書いてあるとおりなのですが、以下のようにすることでwork-aroundしてやります。
@ContextConfiguration(loader = WebDelegatingSmartContextLoader, classes = Application)
これでContextを使いたい場合でもSpockを使ってやることができました。