Xamarin.Forms: Unit Testing the Database

Intro

Xamarin.Forms integrates well with SQLite. It also supports WCF and some other web service frameworks. Whether we are performing CRUD operations on the local database or on a network server, we need to be professional developers and not professional debuggerers. Hence, we should unit test our business logic. This article provides some ideas to consider when unit testing operations that involve CRUD operations.

I recently wrote an app to record my success rate with meeting people:

For the app, I wrote a unit test for an Add operation:

[TestMethod]
public void adding_success_updates_datapoints()
{
    // Setup
    var databaseCreated = new MockDatabase() != null;
    var viewModel = new ViewModel();
 
    // Test
    viewModel.Success.Execute(null);
 
    // Verify
    var datapoint = viewModel.DataPoints.Single();
    var expected = datapoint.Successes == 1 &&
                   datapoint.Failures == 0;
 
    Assert.IsTrue(expected);
}

This unit test targets the view-model and tests the “Success” command that gets triggered by an app’s button press. When this command is executed an insert operation ultimately occurs on a mock database. Notice how I said “MockDatabase”. We use a mock database so that we can ensure that our business logic is correct thus enabling us to troubleshoot integration issues if and when insert operations fail.

ViewModel.commands

Our view-model command can be found here along with another command:

public partial class ViewModel
{
    void ActivateCommands()
    {
        Success = new DelegateCommand(obj => AddDataPoint(true));
        Failed = new DelegateCommand(obj => AddDataPoint(false));
    }
 
    public DelegateCommand Success { get; private set; }
    public DelegateCommand Failed { get; private set; }
}

ViewModel.internal

Our private operations and state for our view-model can be found here:

public partial class ViewModel
{
    DataPointsRepository _repository = new DataPointsRepository();
 
    void AddDataPoint(bool isSuccessful)
    {
        AddEncounter(isSuccessful);
        Refresh();
    }
 
    void AddEncounter(bool isSuccessful)
    {
        var encounter = new Encounter() { IsSuccessful = isSuccessful };
        _repository.Add(encounter);
    }
 
    void Refresh()
    {
        var datapoints = _repository.GetDataPoints();
        DataPoints = new ObservableCollection<DataPoint>(datapoints);
 
        var totalCount = DataPoints.Sum(dp => dp.Successes + dp.Failures);
        var successCount = DataPoints.Sum(dp => dp.Successes);
 
        if (totalCount == 0) return;
 
        SuccessRate = (decimal)successCount / (decimal)totalCount;
        Ratio = $"{successCount} out of {totalCount}";
    }
}

Repository

Our repository class serves as a façade for database operations. This class relies on a database interface to processes CRUD operation results for its client. The bootstrap process required involves making a promise to respond to a database instance sent to this repository object. After the promise is made, the repository sends a database request.

public partial class DataPointsRepository
    {
        public DataPointsRepository()
        {
            MakePromises();
            SendRequests();
        }
 
        public IEnumerable<Encounter> GetEncounters(DateTime date)
        {
            var compareDate = date.ToString(Constants.DATE_FORMAT);
            var result = _database.GetEncounters(compareDate);
 
            if (result == null) return new HashSet<Encounter>();
 
            return result;
        }
 
        public IEnumerable<Encounter> GetEncounters() => _database.GetEncounters();
 
        public IEnumerable<DataPoint> GetDataPoints()
        {
            _datapointDictionary.Clear();
 
            var encounters = GetEncounters();
 
            foreach (var encounter in encounters)
            {
                DataPoint foundDatapoint = null;
                var exists = _datapointDictionary.TryGetValue(encounter.Date, out foundDatapoint);
 
                if (exists)
                {
                    SetValue(encounter, foundDatapoint);
                }
                else
                {
                    var datapoint = new DataPoint(encounter.Date);
 
                    SetValue(encounter, datapoint);
                    _datapointDictionary.Add(datapoint.Date, datapoint);
                }
            }
 
            return _datapointDictionary.Values;
        }
 
        public void Add(Encounter encounter)
        {
            var encounters = GetEncounters(DateTime.Now);
            var todaysEncounters = new HashSet<Encounter>(encounters);
            todaysEncounters.Add(encounter);
 
            _dateEncounters[DateTime.Now.ToString(Constants.DATE_FORMAT)] = todaysEncounters;
 
            _database.Add(encounter);
        }
    }

Repository.internal

Repository.internal maintains internal state and sends a request for a database instance.

public partial class DataPointsRepository
{
    Dictionary<string, HashSet<Encounter>> _dateEncounters = new Dictionary<string, HashSet<Encounter>>();
    Dictionary<string, DataPoint> _datapointDictionary = new Dictionary<string, DataPoint>();
    IDatabase _database = null;
 
    void SetValue(Encounter encounter, DataPoint datapoint)
    {
        if (encounter.IsSuccessful) datapoint.Successes++;
        else datapoint.Failures++;
    }
 
    void OnRequestDatabase(object obj) => _database = obj as IDatabase;
 
    void MakePromises() =>
        Subscribe(Messages.REQUEST_DATABASE_RESPONSE, OnRequestDatabase);
 
    void BreakPromises() =>
        Unsubscribe(Messages.REQUEST_DATABASE_RESPONSE, OnRequestDatabase);
 
    void SendRequests() =>
        Publish(Messages.REQUEST_DATABASE);
}

AbstractDatabase

We use an abstract database to perform common operations that our app’s database and our unit test database require. This abstract class is leveraged so that we can support the DRY principle. Specifically, we declare abstract CRUD operations that need to be implemented from our app’s database and our unit test database. Also in this class, we implement database related subscriptions. When we receive a database request from the outside world, we publish ourselves as the response to the request.

    public abstract class AbstractDatabase : IDatabase
    {
        protected void MakePromises() =>
            Subscribe(Messages.REQUEST_DATABASE, OnRequestDatabase);
 
        protected void BreakPromises() =>
            Unsubscribe(Messages.REQUEST_DATABASE, OnRequestDatabase);
 
        protected void OnRequestDatabase(object obj) =>
            Publish(Messages.REQUEST_DATABASE_RESPONSE, this);
 
        public abstract void Add(Encounter encounter);
 
        public abstract IEnumerable<Encounter> GetEncounters();
 
        public abstract IEnumerable<Encounter> GetEncounters(string compareDate);
    }

IDatabase

In order for us to unit test our repository logic we need an interface to reflect our database operations.

public interface IDatabase
{
    IEnumerable<Encounter> GetEncounters(string compareDate);
    IEnumerable<Encounter> GetEncounters();
    void Add(Encounter encounter);
}

MockDatabase

Here’s the database used within our unit tests. Within the constructor, we make promises to respond to messages we’re interested in and we initialize our database.

public class MockDatabase : AbstractDatabase
{
    HashSet<Encounter> _dateEncounters = new HashSet<Encounter>();
 
    public MockDatabase()
    {
        MakePromises();
        InitializeData();
    }
 
    void InitializeData() { }
 
    public override void Add(Encounter encounter) => _dateEncounters.Add(encounter);
 
    public override IEnumerable<Encounter> GetEncounters() => _dateEncounters;
 
    public override IEnumerable<Encounter> GetEncounters(string compareDate) =>
        _dateEncounters.Where(dp => dp.Date == compareDate);
}

Database

Here’s the database used within the actual application. Within the constructor, we make promises to respond to messages we’re interested in and we initialize our database.

    public class Database : AbstractDatabase
    {
        SQLiteConnection _databaseConnection = null;
 
        public Database()
        {
            MakePromises();
            InitializeData();
        }
 
        void InitializeData()
        {
            _databaseConnection = DependencyService.Get<IDataConnection>().Connect();
 
            var tableExists = DependencyService.Get<IDataConnection>().TableExists(_databaseConnection, "Encounter");
 
            if (!tableExists)
            {
                _databaseConnection.CreateTable<Encounter>();
            }
        }
 
        public override IEnumerable<Encounter> GetEncounters(string compareDate) =>
            _databaseConnection.Table<Encounter>().Where(dp => dp.Date == compareDate);
 
        public override IEnumerable<Encounter> GetEncounters() => _databaseConnection.Table<Encounter>();
 
        public override void Add(Encounter encounter) =>
            _databaseConnection.Insert(encounter);
    }

In conclusion, whether we are performing CRUD operations on the local database or on a network server, we need to be professional developers and not professional debuggerers. Hence, we should unit test our business logic. This article provides some ideas to consider when unit testing operations that involve CRUD operations.

NOTE:

Scott Nimrod is fascinated with Software Craftsmanship.

He loves responding to feedback and encourages people to share his articles.

He can be reached at scott.nimrod @ bizmonger.net

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: