Sometimes, I find it quite not flexible to write a test that require input string in a specific format e.g. JSON string. I prefer wrapping it in a builder class to sacrifice explicitness for flexibility
Considering the test method below, the json input string for the BookOrderBinder.fromJson() is straightforward. This same string will be constructed using javascript on client side so javascript developers could look at this test and know the valid format of the message
@Test
public void fromJsonToBookOrderWithTwoBooks()
throws JsonParseException, JsonMappingException, IOException {
String bookOrderJson =
"{" +
"\"orderId\" : \"Order:210A\"," +
"\"bookList\" : [ " +
"{" +
"\"title\" : \"Algorithm-101\"," +
"\"isbn\" : \"1111\"" +
"}," +
"{" +
"\"title\" : \"Math-intro\"," +
"\"isbn\" : \"2222\"" +
"} " +
"] " +
"}";
BookOrder order = BookOrderBinder.fromJson(bookOrderJson);
assertEquals("Order:210A", order.getOrderId());
assertBook(order.getBookList().get(0), "Algorithm-101", "1111");
assertBook(order.getBookList().get(1), "Math-intro", "2222");
}
This string concatenation test input is hard to maintain. I have to combine many strings together in the way that reflects the structure of the data. Adding and removing elements in string format is error prone. If the name of a field is changed that I have to go through every tests like this to change the name. I could use name constant string but it will make the test even more hard-to-read
“\” + ORDER_ID + \” : \”Order:210A\”,”
I prefer to use builder pattern to prepare this kind of string input. It is far more readable and easier to maintain. The logic to construct the string is encapsulated in a java class which enables the string to be composed with less error prone
@Test
public void fromJsonToBookOrderWithTwoBooks_v2()
throws JsonParseException, JsonMappingException, IOException {
String bookOrderJson = new BookOrderJsonBuilder()
.setOrderId("Order:210A")
.addBook("Algorithm-101", "1111")
.addBook("Math-intro", "2222")
.getJsonString();
System.out.println(bookOrderJson);
BookOrder order = BookOrderBinder.fromJson(bookOrderJson);
assertEquals("Order:210A", order.getOrderId());
assertBook(order.getBookList().get(0), "Algorithm-101", "1111");
assertBook(order.getBookList().get(1), "Math-intro", "2222");
}
What I have lost is the explicitness of the json input format. Developers who responsible for writing Javascript to construct the message may still don’t know exactly how the message looks like by looking at the test but that shouldn’t be a big problem. We could use the builder class to easily learn the exact format at anytime
Here is the example of how I implement the builder class. I am using Jackson API to manipulate JSON object
public class BookOrderJsonBuilder {
public static final String ORDER_ID = "orderId";
public static final String BOOK_LIST = "bookList";
public static final String TITLE = "title";
public static final String ISBN = "isbn";
private ObjectNode bookOrder;
private ObjectMapper mapper;
public BookOrderJsonBuilder(){
mapper = new ObjectMapper();
bookOrder = mapper.createObjectNode();
}
public BookOrderJsonBuilder setOrderId(String id){
bookOrder.put(ORDER_ID, id);
return this;
}
public BookOrderJsonBuilder addBook(String title, String isbn){
getBookListNode().add( newBookNode(title, isbn) );
return this;
}
private ArrayNode getBookListNode(){
if(bookOrder.get(BOOK_LIST) == null ){
bookOrder.putArray(BOOK_LIST);
}
return (ArrayNode)bookOrder.get(BOOK_LIST);
}
private ObjectNode newBookNode(String title, String isbn){
ObjectNode book = mapper.createObjectNode();
book.put(TITLE, title);
book.put(ISBN, isbn);
return book;
}
public String getJsonString()
throws JsonGenerationException, JsonMappingException, IOException{
StringWriter writer = new StringWriter();
mapper.writeValue(writer, bookOrder);
return writer.toString();
}
}
