The Illusion of Persistence: Saving Test Data

I recently introduced the BookRepository, which is a set of a few classes designed generated fake data. The idea is that if you are writing a test or a demo and just need some data objects and don’t want to have to worry about the ins and outs of a real persistence layer – then the BookRepository is just for you.

While displaying read-only fake data is useful, what if you want to simulate fake persistence as well? In a demo I am working on that implements the Live Form Ajax pattern, I needed a way to at least create illusion of persistence.

Adding Persistence

The repository class already includes methods for GetByID, Insert, Update and Delete. The first iteration just had stubs that called System.Diagnositcs.Debug.WriteLine so that you had a place to add a breakpoint during testing. All that I needed to do was add some code that would “do something” to persist the objects without a database.

The solution that seems to work well is to add a Hashtable into a Cache entry. Objects IDs are used as the keys in the Hashtable and the objects themselves are loaded into the value. To handle the grunt work of this interaction I create a new class: CacheMockPersistence.

CacheMockPersistence

The CacheMockPersistence class is a simple class and also features methods named Insert, Update, Delete and this time GetByKey. The cache key is a GUID so if you use it in an existing application it shouldn’t stomp on anything you are doing.

Here is the code:

public class CacheMockPersistence
{
    private Hashtable _data = null;
    private HttpContext _context = null;
 
    private const string DATA_STORE_KEY = "{2E54BB16-3220-4B49-9932-1455C4014E5B}";
 
    private Hashtable Data
    {
        get 
        {
            if (this._context.Cache[DATA_STORE_KEY] == null)
            {
                this._context.Cache.Insert(DATA_STORE_KEY, new Hashtable());
            }
            return ((Hashtable)this._context.Cache[DATA_STORE_KEY]);
        }
    } 
 
    public CacheMockPersistence(HttpContext context)
    {
        this._context = context;
    }
 
    public void Insert(object key, object value)
    {
        this.Data.Add(key, value);
    }
 
    public void Update(object key, object value)
    {
        this.Data[key] = value;
    }
 
    public void Delete(object key)
    {
        this.Data.Remove(key);
    }
 
    public object GetByKey(object key)
    {
        object value = new object();
 
        if (this.Data[key] != null)
        {
            value = this.Data[key];    
        }
        return value;
    }
}

Here is the updated BookRepository that is hooked up to CacheMockPersistence:

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
 
[Serializable]
public class Book
{
    public int ID { get; set; }
    public string Title { get; set; }
    public string Author { get; set; }
    public string Url { get; set; }
    public double Price { get; set; }
    public DateTime PublishDate { get; set; }
 
    public string PriceFormatted
    {
        get { return string.Format("{0:C}", Price); }
    }
 
    public string PublishDateShort
    {
        get { return PublishDate.ToShortDateString(); }
    }
 
    public Book() { }
 
    public static Book Create(int id)
    {
        Book bk = new Book();
        Random random = new Random();
 
        bk.ID = id;
        bk.Title = string.Format("Title of Book {0}", id);
        bk.Author = string.Format("Author {0}", id);
        bk.Url = string.Format("http://site.com/book/{0}/", id);
        bk.Price = Convert.ToDouble(string.Format("{0:##.##}", random.Next(5, 35) + random.NextDouble()));
        bk.PublishDate = Convert.ToDateTime(string.Format("{0}/{1}/{2}", random.Next(1, 12), random.Next(1, 28), random.Next(1990, DateTime.Now.Year)));
 
        return bk;
    }
}
 
/// <summary>
/// Summary description for BookRepository
/// </summary>
public class BookRepository
{
    private static BookRepository _instance;
    private CacheMockPersistence _persistence = null;
    private bool _hasPersistence = false;
 
    public static BookRepository Instance
    {
        get { return _instance; }
    }
 
    public BookRepository()
    {
        if (HttpContext.Current != null)
        {
            this._persistence = new CacheMockPersistence(HttpContext.Current);
            this._hasPersistence = true;
        }
    }
 
    static BookRepository()
    {
        _instance = new BookRepository();
    }
 
    /// <summary>
    /// Gets a Book object by the supplied ID. If the object does not exist it will create a new instance.
    /// </summary>
    public Book GetByID(int id)
    {
        object value;
        Book item = null;
 
        if (this._hasPersistence)
        {
            value = this._persistence.GetByKey(id);
            if (value is Book)
            {
                item = (Book)value;
            }
            else
            {
                item = Book.Create(id);
                this._persistence.Insert(id, item);
            }
        }
        else
        {
            item = Book.Create(id);
        }
 
        return item;
    }
 
    /// <summary>
    /// Returns a List of 10 Book objects
    /// </summary>
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public List<Book> GetBooks()
    {
        return this.GetBooks(10);
    }
 
    /// <summary>
    /// Mocks insertion into a persistence layer
    /// </summary>
    [DataObjectMethod(DataObjectMethodType.Insert, true)]
    public void Insert(string title, string author, string url, double price, DateTime publishDate)
    {
        Debug.WriteLine("Insert object into database");
 
        if (this._hasPersistence)
        {
            Random r = new Random();
            int id = r.Next(10000,90000);
            Book value = Book.Create(id);
            this._persistence.Insert(id, value);
        }
    }
 
    /// <summary>
    /// Mocks update to a persistence layer
    /// </summary>
    public void Update(Book book)
    {
        Debug.WriteLine("Update database");
 
        if (this._hasPersistence)
        {
            this._persistence.Update(book.ID, book);
        }
    }
 
    /// <summary>
    /// Mocks update to a persistence layer
    /// </summary>
    [DataObjectMethod(DataObjectMethodType.Update, true)]
    public void Update(int ID, string title, string author, string url, double price, DateTime publishDate)
    {
        Book b = Book.Create(ID);
        b.Title = title;
        b.Author = author;
        b.Url = url;
        b.Price = price;
        b.PublishDate = publishDate;
        this.Update(b);
    }
 
    /// <summary>
    /// Mocks deleting from a persistence layer
    /// </summary>
    [DataObjectMethod(DataObjectMethodType.Delete, true)]
    public void Delete(int ID)
    {
        Debug.WriteLine("Delete from database");
 
        if (this._hasPersistence)
        {
            this._persistence.Delete(ID);
        }
    }
 
    /// <summary>
    /// Returns a list of Book objects where you decide the quantity
    /// </summary>
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public List<Book> GetBooks(int numberOfBooks)
    {
        List<Book> books = new List<Book>();
 
        for (int i = 1; i < (numberOfBooks + 1); i++)
        {
            books.Add(Book.Create(i));
        }
 
        return books;
    }
 
    /// <summary>
    /// Returns 10 book records in a DataSet
    /// </summary>
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public DataSet GetBooksAsDataSet()
    {
        return this.GetBooksAsDataSet(10);
    }
 
    /// <summary>
    /// Returns a series of books in a DataSet
    /// </summary>
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public DataSet GetBooksAsDataSet(int numberOfBooks)
    {
        DataSet ds = new DataSet();
        DataTable dt;
 
        dt = new DataTable();
        dt.Columns.Add(new DataColumn("ID"));
        dt.Columns.Add(new DataColumn("Title"));
        dt.Columns.Add(new DataColumn("Author"));
        dt.Columns.Add(new DataColumn("Price"));
        dt.Columns.Add(new DataColumn("Url"));
        dt.Columns.Add(new DataColumn("PublishDate"));
 
        for (int i = 1; i < (numberOfBooks + 1); i++)
        {
            this.AddNewRow(ref dt, i);
        }
 
        ds.Tables.Add(dt);
        return ds;
    }
 
    private void AddNewRow(ref DataTable dt, int index)
    {
        DataRow dr = dt.NewRow();
        Book bk = Book.Create(index);
        dr["ID"] = index;
        dr["Title"] = bk.Title;
        dr["Author"] = bk.Author;
        dr["Price"] = bk.Price;
        dr["Url"] = bk.Url;
        dr["PublishDate"] = bk.PublishDate;
        dt.Rows.Add(dr);
    }
}
 
public class CacheMockPersistence
{
    private Hashtable _data = null;
    private HttpContext _context = null;
 
    private const string DATA_STORE_KEY = "{2E54BB16-3220-4B49-9932-1455C4014E5B}";
 
    private Hashtable Data
    {
        get 
        {
            if (this._context.Cache[DATA_STORE_KEY] == null)
            {
                this._context.Cache.Insert(DATA_STORE_KEY, new Hashtable());
            }
            return ((Hashtable)this._context.Cache[DATA_STORE_KEY]);
        }
    } 
 
    public CacheMockPersistence(HttpContext context)
    {
        this._context = context;
    }
 
    public void Insert(object key, object value)
    {
        this.Data.Add(key, value);
    }
 
    public void Update(object key, object value)
    {
        this.Data[key] = value;
    }
 
    public void Delete(object key)
    {
        this.Data.Remove(key);
    }
 
    public object GetByKey(object key)
    {
        object value = new object();
 
        if (this.Data[key] != null)
        {
            value = this.Data[key];    
        }
        return value;
    }
}

I like to keep all this code in a file named BookRepository.cs to make it easy to add to an application.

What do you think? Would you suggest any changes?

No Comments