Testeando Serialización a JSON

2016-02-01

Hace una semana aproximadamente tuvimos un descuido con la serialización a JSON. Teníamos una estructura de datos (un objeto pero sin comportamiento) bastante simple. Debido a su sencillez, decidimos no crear su correspondiente DTO ya que podíamos pasarlo directamente por la red (puede parecer una decisión más o menos acertada según el punto de vista). Trataré de exponer un caso sencillo para explicar el problema. Esta podría ser la clase de la que hablamos:

1
2
3
4
5
6
7
8
9
public class Book {
public string Author {get; private set;}
public string Title {get; private set;}
public Book(string author, string title) {
Author = author;
Title = title;
}
}

Supongamos que en este caso ambas propiedas son parte del identificador del libro. Es decir, no pueden existir dos libros con el mismo Author y el mismo Title. Esta clase permanece ahí un tiempo. Pero un día nos damos cuenta de que sería una mejor estrategia de diseño sacar un objeto que represente ese Id. De manera que quedaría así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BookId {
public string Author {get; private set;}
public string Title {get; private set;}
public BookId(string author, string title) {
Title = title;
Author = author;
}
}
public class Book {
private readonly BookId bookId;
public string Author => bookId.Author;
public string Title => bookId.Title;
public Book(BookId bookId) {
this.bookId = bookId;
}
}

Debido a que estas propiedades se usaban en bastantes sitios y que además tiene sentido pedir a un libro su Author y su Title, decidimos seguir manteniendo los getters públicos de estas propiedades. Lanzamos los tests, todo verde. No obstante, habíamos introducido un bug. Que detectamos más tarde de lo que nos habría gustado. Como dije anteriormente este objeto Book se está pasando por la red. Es decir, se serializa a JSON y se deserializa en el cliente con el cual (en nuestro caso) compartimos tecnología. Este objeto para nosotros era parte de un Agregado, el cuál tenía su representación DTO. Este DTO tenía un Book (en lugar de un BookDto ya que no existía aún). Provocando un bug en la deserialización:

1
2
3
4
5
6
7
var book = new Book(new BookId("Pedro", "Viviendo!"));
// Resultado de la Serialización a JSON
{
"Author": "Pedro",
"Title": "Viviendo!"
}

Cuando tratemos de deserializar esto. El framework irá a la clase Book y buscará alguna de las siguientes: dos propiedades Author y Title con public setter ó un constructor que reciba dos parámetros de tipo string llamados author y title. No encontrará ninguna de estas opciones y lo que hará es llamar al constructor de book pasando el valor por defecto por parámetro. En este caso para un objeto (BookId) dicho será null.

La verdad que el error no fue fácil de encontrar, al menos en mi caso. Para tratar de evitarlo decidimos escribir tests que simulen la serialización y deserialización de estos objetos. Pero no nos compensa hacerlo para aquellas clases que desde un primer momento tienen su correspondiente dto. Así que en nuestro caso, cuando nos surja un objeto de negocio tan sencillo como para pasarlo por la red, escribiremos un test para esta serialización. Para que el día que ese objeto evolucione tengamos un test que nos respalde.

1
2
3
4
5
6
7
8
9
[Test]
public void book_json_serialization() {
var book = new Book(new BookId("Author", "Title"));
var serializedBook = JsonConvert.SerializeObject(book);
var deserializedBook = JsonConvert.DeserializeObject<Book>(serializedBook);
deserializedBook.ShouldBeEquivalentTo(book);
}