コンピュータクワガタ

かっぱのかっぱによるコンピュータ関連のサイトです

Spring MVC 3.2のJSONのテスト

以前、Spring MVC 3.2のSpring MVC Testを触ったでSpring MVC 3.2から導入されたSpring MVCのテストを紹介しましたが、今回はJSONのテストについて紹介します。

今回のアプリケーションもGitHubに上げています。いろいろなSpringのサンプルをまとめています。
https://github.com/kuwalab/SpringSample

Eclipse 4.3でMaven+WTPのプロジェクトとして作成していますので、その環境であればプロジェクトをimportすることですぐに使えます。

サンプル中のspring_mvc32_json_testフォルダをEclipseのプロジェクトとしてインポートしてください。

テスト用のアプリケーション

テスト用のアプリケーションとして、簡易書籍管理アプリケーションを作ります。今回はJSONのテストのため、RESTでアクセスしてCRUDをひと通り行います。

Mavenの設定

Springは依存ライブラリーが多い上、JSON対応でもライブラリーが必要になるためMavenで管理したほうが楽です。

pom.xmlの全体は以下になります。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.example.spring_mvc32_json_test</groupId>
 <artifactId>spring-mvcjsontest</artifactId>
 <packaging>war</packaging>
 <version>0.0.1-SNAPSHOT</version>
 <name>mvcjsontest Maven Webapp</name>
 <url>http://maven.apache.org</url>
 <build>
  <finalName>spring_mvc32_json_test</finalName>
  <pluginManagement>
   <plugins>
    <plugin>
     <artifactId>maven-compiler-plugin</artifactId>
     <configuration>
      <source>1.7</source>
      <target>1.7</target>
      <encoding>UTF-8</encoding>
     </configuration>
    </plugin>
   </plugins>
  </pluginManagement>
 </build>
 <dependencies>
  <!-- Spring Framework -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <version>${spring.version}</version>
   <scope>test</scope>
  </dependency>
  <!-- AspectJ -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>${aspectJ.version}</version>
  </dependency>
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>${aspectJ.version}</version>
  </dependency>
  <!-- JUnit -->
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>${junit.version}</version>
   <scope>test</scope>
  </dependency>
  <!-- Servlet -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
   <version>3.0.1</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet.jsp</groupId>
   <artifactId>jsp-api</artifactId>
   <version>2.2</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
   <version>1.2</version>
  </dependency>
  <dependency>
   <groupId>javax.validation</groupId>
   <artifactId>validation-api</artifactId>
   <version>1.0.0.GA</version>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>4.3.0.Final</version>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator-annotation-processor</artifactId>
   <version>4.3.0.Final</version>
  </dependency>
  <dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>1.0.11</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>jcl-over-slf4j</artifactId>
   <version>1.7.5</version>
  </dependency>
  <dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-core</artifactId>
   <version>2.1.3</version>
  </dependency>
  <dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.1.3</version>
  </dependency>
  <dependency>
   <groupId>com.jayway.jsonpath</groupId>
   <artifactId>json-path</artifactId>
   <version>0.8.1</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.hamcrest</groupId>
   <artifactId>hamcrest-all</artifactId>
   <version>1.3</version>
   <scope>test</scope>
  </dependency>
 </dependencies>
 <properties>
  <spring.version>3.2.6.RELEASE</spring.version>
  <aspectJ.version>1.7.1</aspectJ.version>
  <junit.version>4.11</junit.version>
 </properties>
</project>

Spring自体やSpring MVC、test関連は前回も取り上げたとおりです。今回は、JSONの読み書きをするためにSpringではJacksonライブラリーを使用するのが簡単です。

Jacksonは以下の2つのライブラリーが必要です。

<dependency>
 <groupId>com.fasterxml.jackson.core</groupId>
 <artifactId>jackson-core</artifactId>
 <version>2.1.3</version>
</dependency>
<dependency>
 <groupId>com.fasterxml.jackson.core</groupId>
 <artifactId>jackson-databind</artifactId>
 <version>2.1.3</version>
</dependency>

また、JSONのテストをする際に、[json-path:https://code.google.com/p/json-path/]というライブラリーを使います。

<dependency>
 <groupId>com.jayway.jsonpath</groupId>
 <artifactId>json-path</artifactId>
 <version>0.8.1</version>
 <scope>test</scope>
</dependency>

最後に、今回はJUnit4に付属のMatcherでは足りなかったためhamcrestライブラリーも入れています。

<dependency>
 <groupId>org.hamcrest</groupId>
 <artifactId>hamcrest-all</artifactId>
 <version>1.3</version>
 <scope>test</scope>
</dependency>
書籍管理アプリケーション

書籍情報のCRUDはRESTに従い以下のURLでアクセスします。

メソッド URL アクション
GET /books 全書籍情報の取得
POST /books 書籍の新規登録
PUT /books/{書籍ID} 書籍IDの書籍情報の更新
DELETE /books/{書籍ID} 書籍IDの書籍情報の削除

データの取得は上記のURLでアクセスしますが、管理画面として/indexを使用します。

アプリケーションのコード

web.xmlやSpringの設定に関してはGitHubのコードを見ていただくとして、ControllerとJSPJavaScriptのコードだけ確認します。

まずControllerです。

package com.example.spring.mvcjson;

import java.util.ArrayList;
import java.util.List;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class BookController {
    private static List<Book> bookList;

    static {
        bookList = new ArrayList<>();
        bookList.add(new Book("1", "よくわかるSpring", 3000));
        bookList.add(new Book("2", "よくわかるJava", 3200));
        bookList.add(new Book("3", "よくわかるJUnit", 2800));
    }

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index() {
        return "book/index";
    }

    @RequestMapping(value = "/books", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE
            + ";charset=utf-8")
    @ResponseBody
    public List<Book> books() {
        return bookList;
    }

    @RequestMapping(value = "/books", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE
            + ";charset=utf-8")
    @ResponseBody
    public Book insert(@RequestBody Book book) {
        bookList.add(book);
        return book;
    }

    @RequestMapping(value = "/books/{bookId}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE
            + ";charset=utf-8")
    @ResponseBody
    public Book update(@PathVariable("bookId") String initBookId,
            @RequestBody Book book) {
        for (int i = 0; i < bookList.size(); i++) {
            Book currentBook = bookList.get(i);
            if (currentBook.getBookId().equals(initBookId)) {
                currentBook.setBookId(book.getBookId());
                currentBook.setBookName(book.getBookName());
                currentBook.setPrice(book.getPrice());
                return currentBook;
            }
        }

        return new Book();
    }

    @RequestMapping(value = "/books/{bookId}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE
            + ";charset=utf-8")
    @ResponseBody
    public Book delete(@PathVariable String bookId) {
        for (int i = 0; i < bookList.size(); i++) {
            Book book = bookList.get(i);
            if (book.getBookId().equals(bookId)) {
                bookList.remove(i);
                return book;
            }
        }

        return new Book();
    }
}

次に、JSPです。

<%@page contentType="text/html; charset=utf-8" %><%--
--%><!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>書籍情報</title>
 </head>
 <body>
  <table>
   <thead>
    <tr>
     <th><input type="text" size="3" placeholder="id" id="insBookId"></th>
     <th><input type="text" size="20" placeholder="書名" id="insBookName"></th>
     <th><input type="text" size="5" placeholder="価格" id="insPrice"></th>
     <th><input type="button" value="追加" id="insert"></th>
    </tr>
   </thead>
   <tbody>
   </tbody>
  </table>
  <script src="js/lib/jquery-2.0.3.min.js"></script>
  <script src="js/lib/underscore-1.5.1.min.js"></script>
  <script src="js/book.js"></script>
 </body>
</html>

最後にJavaScriptです。

var JST = {};
JST['tr'] = _.template(
  '<tr>' +
  '<th>' +
   '<input type="hidden" name="initBookId" value="<%- bookId %>">' +
   '<input type="text" size="3" placeholder="id" name="bookId" value="<%- bookId %>">' +
  '</th>' +
  '<th><input type="text" size="20" placeholder="書名" name="bookName" value="<%- bookName %>"></th>' +
  '<th><input type="text" size="5" placeholder="価格" name="price" value="<%- price %>"></th>' +
  '<th><input type="button" value="更新" name="update"><input type="button" value="削除" name="delete"></th>' +
  '</tr>'
);

var $tbody = $('tbody');

// 登録済みの一覧を読み直す
var getBookList = function() {
  $.ajax({
    method: 'get',
    url: 'books',
    dataType: 'json'
  }).done(function(data) {
    var i;
    $tbody.empty();
    for (i = 0; i < data.length; i++) {
      $tbody.append(JST['tr'](data[i]));
    }
  }).fail(function(jqXHR, textStatus, errorThrown) {
    console.log(textStatus);
  });
};

var setTrEvent = function() {
  // 行ごとの更新ボタンの処理
  $('table').on('click', 'input[name="update"]', function() {
    var $tr = $(this).parents('tr');
    var sendData = {
      bookId: $tr.find('input[name="bookId"]').val(),
      bookName: $tr.find('input[name="bookName"]').val(),
      price: $tr.find('input[name="price"]').val()
    };

    var initBookId = $tr.find('input[name="initBookId"]').val();

    $.ajax({
      method: 'put',
      contentType: 'application/json;charset=utf-8',
      data: JSON.stringify(sendData),
      url: 'books/' + initBookId,
      dataType: 'json'
    }).done(function(data) {
      getBookList();
    }).fail(function(jqXHR, textStatus, errorThrown) {
      console.log(textStatus);
    });
  });

  // 行ごとのdeleteボタンの処理
  $('table').on('click', 'input[name="delete"]', function() {
    var bookId = $(this).parents('tr').find('input[name="initBookId"]').val();
    $.ajax({
      method: 'delete',
      url: 'books/' + bookId,
      dataType: 'json'
    }).done(function(data) {
      getBookList();
    }).fail(function(jqXHR, textStatus, errorThrown) {
     console.log(textStatus);
    });
  });
};

getBookList();
setTrEvent();

$('#insert').on('click', function() {
  var sendData = {
      bookId: $('#insBookId').val(),
      bookName: $('#insBookName').val(),
      price: $('#insPrice').val()
  };

  $.ajax({
    method: 'post',
    contentType: 'application/json;charset=utf-8',
    data: JSON.stringify(sendData),
    url: 'books',
    dataType: 'json'
  }).done(function(data) {
    $('#insBookId').val('');
    $('#insBookName').val('');
    $('#insPrice').val('');
    getBookList();
  }).fail(function(jqXHR, textStatus, errorThrown) {
    console.log(textStatus);
  });
});
データの読み込み

/コンテキストパス/にアクセスすると、データを読み込みます。データはJavaScriptのgetBookList関数で読み込んでいます。

$.ajax({
  method: 'get',
  url: 'books',
  dataType: 'json'
}).done(function(data) {
  var i;
  $tbody.empty();
  for (i = 0; i < data.length; i++) {
    $tbody.append(JST['tr'](data[i]));
  }
  //

関数では、/booksにgetリクエストを投げ、戻り値をJSONとして受け取ります。

受け取ったあとは、Underscore.jsのテンプレート機能でレンダリングしています。

データの登録

データの登録は追加ボタンを押すことで、postリクエストを投げます。データの読み込みと違い、登録するデータをJSONで送信します。

$('#insert').on('click', function() {
  var sendData = {
      bookId: $('#insBookId').val(),
      bookName: $('#insBookName').val(),
      price: $('#insPrice').val()
  };

  $.ajax({
    method: 'post',
    contentType: 'application/json;charset=utf-8',
    data: JSON.stringify(sendData),
    url: 'books',
    dataType: 'json'
  }).done(function(data) {
    $('#insBookId').val('');
    $('#insBookName').val('');
    $('#insPrice').val('');
    getBookList();
  }).fail(function(jqXHR, textStatus, errorThrown) {
    console.log(textStatus);
  });
});

データの登録に成功した場合には、入力フォームの情報を削除しデータを再度読み込みます。

データの更新

データの更新は、更新ボタンを押すことでputリクエストを投げます。更新はURLのパラメータに更新するIDを埋め込み、更新するデータはJSONデータとして送信します。

書籍ID自体も変更することができるため、データを読み込んだ辞典での書籍IDをhiddenパラメータとして保管しておきURLに埋め込む書籍IDはそのデータを使用します。画面上に表示される書籍IDは更新する値となります。

$('table').on('click', 'input[name="update"]', function() {
  var $tr = $(this).parents('tr');
  var sendData = {
    bookId: $tr.find('input[name="bookId"]').val(),
    bookName: $tr.find('input[name="bookName"]').val(),
    price: $tr.find('input[name="price"]').val()
  };

  var initBookId = $tr.find('input[name="initBookId"]').val();

  $.ajax({
    method: 'put',
    contentType: 'application/json;charset=utf-8',
    data: JSON.stringify(sendData),
    url: 'books/' + initBookId,
    dataType: 'json'
  }).done(function(data) {
    getBookList();
  }).fail(function(jqXHR, textStatus, errorThrown) {
    console.log(textStatus);
  });
});
データの削除

データの削除はupdateと同じようにhidden項目の書籍IDを用いてデータを削除します。

JSONのテスト

テストコードの全体。

package com.example.spring.mvcjson;

import static org.hamcrest.Matchers.*;
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.*;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
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 com.fasterxml.jackson.databind.ObjectMapper;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/beans-webmvc.xml" })
public class BookControllerTest {
    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = webAppContextSetup(wac).build();
    }

    /* JSONPath http://goessner.net/articles/JsonPath/ */
    @Test
    public void slash_booksのGET() throws Exception {
        mockMvc.perform(get("/books")).andExpect(jsonPath("$").isArray())
                .andExpect(jsonPath("$", hasSize(3)))
                .andExpect(jsonPath("$[0].bookId").value("1"))
                .andExpect(jsonPath("$[0].bookName").value("よくわかるSpring"))
                .andExpect(jsonPath("$[0].price").value(3000));
    }

    @Test
    public void slash_booksのPOST() throws Exception {
        Book book = new Book("123", "よくわかるJSON", 2999);
        ObjectMapper mapper = new ObjectMapper();
        String jsonStr = mapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(book);
        mockMvc.perform(
                post("/books").contentType(MediaType.APPLICATION_JSON).content(
                        jsonStr.getBytes()))
                .andExpect(jsonPath("$.bookId").value(book.getBookId()))
                .andExpect(jsonPath("$.bookName").value(book.getBookName()))
                .andExpect(jsonPath("$.price").value(book.getPrice()));

        // 追加されているか確認
        mockMvc.perform(get("/books")).andExpect(jsonPath("$").isArray())
                .andExpect(jsonPath("$", hasSize(4)))
                .andExpect(jsonPath("$[3].bookId").value(book.getBookId()))
                .andExpect(jsonPath("$[3].bookName").value(book.getBookName()))
                .andExpect(jsonPath("$[3].price").value(book.getPrice()));

        // 追加したものを削除しておく
        mockMvc.perform(delete("/books/" + book.getBookId(), "json"));

        // 削除されているか確認
        mockMvc.perform(get("/books")).andExpect(jsonPath("$").isArray())
                .andExpect(jsonPath("$", hasSize(3)));
    }

    @Test
    public void slash_booksのPUT() throws Exception {
        Book book = new Book("5", "よくわかるJava 7", 4000);
        ObjectMapper mapper = new ObjectMapper();
        String jsonStr = mapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(book);
        mockMvc.perform(
                put("/books/2").contentType(MediaType.APPLICATION_JSON)
                        .content(jsonStr.getBytes()))
                .andExpect(jsonPath("$.bookId").value(book.getBookId()))
                .andExpect(jsonPath("$.bookName").value(book.getBookName()))
                .andExpect(jsonPath("$.price").value(book.getPrice()));

        // 変更されているか確認
        mockMvc.perform(get("/books")).andExpect(jsonPath("$").isArray())
                .andExpect(jsonPath("$", hasSize(3)))
                .andExpect(jsonPath("$[1].bookId").value(book.getBookId()))
                .andExpect(jsonPath("$[1].bookName").value(book.getBookName()))
                .andExpect(jsonPath("$[1].price").value(book.getPrice()));

        // 追加したものをもとに戻しておく
        book = new Book("2", "よくわかるJava", 3200);
        jsonStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(
                book);
        mockMvc.perform(
                put("/books/5").contentType(MediaType.APPLICATION_JSON)
                        .content(jsonStr.getBytes()))
                .andExpect(jsonPath("$.bookId").value(book.getBookId()))
                .andExpect(jsonPath("$.bookName").value(book.getBookName()))
                .andExpect(jsonPath("$.price").value(book.getPrice()));
    }
}
読み込みデータのテスト

データを取得は、/booksに対してgetリクエストを投げます。レスポンスはJSONで帰ってきますが、そのテストはjson-pathを使います。

@Test
public void slash_booksのGET() throws Exception {
    mockMvc.perform(get("/books"))
            .andExpect(jsonPath("$").isArray())
            .andExpect(jsonPath("$", hasSize(3)))
            .andExpect(jsonPath("$[0].bookId").value("1"))
            .andExpect(jsonPath("$[0].bookName").value("よくわかるSpring"))
            .andExpect(jsonPath("$[0].price").value(3000));
}

レスポンスは、以下のように書籍情報の配列として取得できます。

[{"bookId":"1","bookName":"よくわかるSpring","price":3000},
{"bookId":"2","bookName":"よくわかるJava","price":3200},
{"bookId":"3","bookName":"よくわかるJUnit","price":2800}]

json-pathは以下のドキュメントを参考にしてください。
https://code.google.com/p/json-path/

簡単に解説すると、$がルート要素を表します。今回は配列のため$[0]で最初の書籍、$[1]で2番目の書籍を表します。また、オブジェクトのアクセスは「.」を用いて行えます。最初の書籍の書籍IDは$[0].bookIdのようにアクセスできます。

json-pathを使うためには、MockMvcResultMatchers#jsonPathを使用します。

jsonPathの引数には上記のjson-pathの式を入れます。戻り値はJsonPathResultMathersとなります。

JsonPathResultMathersの主なメソッドは以下となります。

メソッド 説明
ResultMatcher doesNotExist() 指定のpathが存在しない
ResultMatcher exists() 指定のpathが存在する
ResultMatcher isArray() 指定のpathが配列
ResultMatcher value(org.hamcrest.Matcher matcher) 取得した結果をmatcherで検査
ResultMatcher value(Object expectedValue) 取得した結果をexpectedValueで検査
データの変更のテスト

データの追加、更新、削除はまとめて行っています。staticフィールドに対して追加するので、追加後に削除してもとに戻しています。

@Test
public void slash_booksのPOST() throws Exception {
    Book book = new Book("123", "よくわかるJSON", 2999);
    ObjectMapper mapper = new ObjectMapper();
    String jsonStr = mapper.writerWithDefaultPrettyPrinter()
            .writeValueAsString(book);
    mockMvc.perform(
            post("/books").contentType(MediaType.APPLICATION_JSON).content(
                    jsonStr.getBytes()))
            .andExpect(jsonPath("$.bookId").value(book.getBookId()))
            .andExpect(jsonPath("$.bookName").value(book.getBookName()))
            .andExpect(jsonPath("$.price").value(book.getPrice()));

    // 追加されているか確認
    mockMvc.perform(get("/books")).andExpect(jsonPath("$").isArray())
            .andExpect(jsonPath("$", hasSize(4)))
            .andExpect(jsonPath("$[3].bookId").value(book.getBookId()))
            .andExpect(jsonPath("$[3].bookName").value(book.getBookName()))
            .andExpect(jsonPath("$[3].price").value(book.getPrice()));

    // 追加したものを削除しておく
    mockMvc.perform(delete("/books/" + book.getBookId(), "json"));

    // 削除されているか確認
    mockMvc.perform(get("/books")).andExpect(jsonPath("$").isArray())
            .andExpect(jsonPath("$", hasSize(3)));
}

@Test
public void slash_booksのPUT() throws Exception {
    Book book = new Book("5", "よくわかるJava 7", 4000);
    ObjectMapper mapper = new ObjectMapper();
    String jsonStr = mapper.writerWithDefaultPrettyPrinter()
            .writeValueAsString(book);
    mockMvc.perform(
            put("/books/2").contentType(MediaType.APPLICATION_JSON)
                    .content(jsonStr.getBytes()))
            .andExpect(jsonPath("$.bookId").value(book.getBookId()))
            .andExpect(jsonPath("$.bookName").value(book.getBookName()))
            .andExpect(jsonPath("$.price").value(book.getPrice()));

    // 変更されているか確認
    mockMvc.perform(get("/books")).andExpect(jsonPath("$").isArray())
            .andExpect(jsonPath("$", hasSize(3)))
            .andExpect(jsonPath("$[1].bookId").value(book.getBookId()))
            .andExpect(jsonPath("$[1].bookName").value(book.getBookName()))
            .andExpect(jsonPath("$[1].price").value(book.getPrice()));

    // 追加したものをもとに戻しておく
    book = new Book("2", "よくわかるJava", 3200);
    jsonStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(
            book);
    mockMvc.perform(
            put("/books/5").contentType(MediaType.APPLICATION_JSON)
                    .content(jsonStr.getBytes()))
            .andExpect(jsonPath("$.bookId").value(book.getBookId()))
            .andExpect(jsonPath("$.bookName").value(book.getBookName()))
            .andExpect(jsonPath("$.price").value(book.getPrice()));
}
まとめ

超ざっくりとですが、JSONのリクエスト、レスポンスのテストも簡単に行えることがわかると思います。

特に最近ではJSONを扱うことが多いと思います。テストも含めて簡単にできるのはかなりメリットがあると思います。