Easy DataTable mocking for a simpler unit test in .NET Core
Introduction
Let’s face it, it all happened to us in our developer’s life to abandon a unit test because it was too tedious. For example the amount of data to mock was too great, but also, with some particularly heavy objects such as datatables are difficult to populate. Example of a DataTable containing only three records of three columns:
DataTable table = new DataTable("MyTable"); | |
DataColumn idColumn = new DataColumn("id", typeof(int)); | |
DataColumn amountColumn = new DataColumn("amount", typeof(decimal)); | |
DataColumn dateColumn = new DataColumn("date", typeof(DateTime)); | |
table.Columns.Add(idColumn); | |
table.Columns.Add(amountColumn); | |
table.Columns.Add(dateColumn); | |
DataRow newRow = table.NewRow(); | |
newRow["id"] = 1; | |
newRow["amount"] = 10.3m; | |
newRow["date"] = new DateTime(2018, 10, 20); | |
table.Rows.Add(newRow); | |
newRow = table.NewRow(); | |
newRow["id"] = 2; | |
newRow["amount"] = 42.1m; | |
newRow["date"] = new DateTime(2018, 04, 12); | |
table.Rows.Add(newRow); | |
newRow = table.NewRow(); | |
newRow["id"] = 2; | |
newRow["amount"] = 5.6; | |
newRow["date"] = new DateTime(2018, 07, 2); | |
table.Rows.Add(newRow); |
As you can see, if you have more columns and more rows, your test will be a complete mess.
In this article we will see how we can simplify this mess for a better and cleaner test. We will use XUnit, NSubstitute and ToExpectedObject for the demonstration
Scenario explanation
Let’s consider we want to test a TransactionService that consumes a TransactionRepository, this last one returns a DataTable. We need to mock this DataTable that contains three columns (Id, Amount and Date). Once done we want to be able to test the mapping made by TransactionService (DataTable to List‹Transaction›)
Transaction object signature:
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading.Tasks; | |
namespace WebApiDemo.Data | |
{ | |
public class Transaction | |
{ | |
public int TransactionId { get; set; } | |
public decimal TransactionAmount { get; set; } | |
public DateTime TransactionDate { get; set; } | |
} | |
} |
Using JsonConvert.DeserializeObject
JsonConvert.DeserializeObject method (NewtonSoft) is able to deserialize JSON object to DataTable. Let’s build an helper that can open JSON files and deserialize it in any object (let’s make the helper generical in the meantime).
using Newtonsoft.Json; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Text; | |
namespace UnitTests | |
{ | |
public static class EmbeddedJsonFileHelper | |
{ | |
public static T GetContent<T>(string filename) | |
{ | |
return JsonConvert.DeserializeObject<T>(File.ReadAllText($"{filename}.json")); | |
} | |
} | |
} |
Setup Json files
Let’s create DataTable mock and Transactions mock within JSON files included as “Embedded files” in your test project.
DataTable mock:
[ | |
{ | |
"id": 1, | |
"amount": 10.3, | |
"date": "2018-10-20T00:00:00" | |
}, | |
{ | |
"id": 2, | |
"amount": 42.1, | |
"date": "2018-04-12T00:00:00" | |
}, | |
{ | |
"id": 3, | |
"amount": 5.6, | |
"date": "2018-07-02T00:00:00" | |
} | |
] |
List of transaction mock:
[ | |
{ | |
"transactionid": 1, | |
"transactionamount": 10.3, | |
"transactiondate": "2018-10-20T00:00:00" | |
}, | |
{ | |
"transactionid": 2, | |
"transactionamount": 42.1, | |
"transactiondate": "2018-04-12T00:00:00" | |
}, | |
{ | |
"transactionid": 3, | |
"transactionamount": 5.6, | |
"transactiondate": "2018-07-02T00:00:00" | |
} | |
] |
Coding the unit test
Once we have mocked your input data (DataTable) and the output expected (List of transactions), we are able to code our test.
Step 1 :
Let’s use the helper to arrange our mocks
Step 2:
Let’s configure the repository with NSubstitute to return our DataTable mock
Step 3:
Let’s invoke the transaction service to get the expected transactions
Step 4:
Invoke ToExpectedObject “ShouldEqual” assertion method
using ExpectedObjects; | |
using Newtonsoft.Json; | |
using NSubstitute; | |
using System; | |
using System.Collections.Generic; | |
using System.Data; | |
using System.Text; | |
using WebApiDemo.BLL; | |
using WebApiDemo.DAL; | |
using WebApiDemo.Data; | |
using Xunit; | |
namespace UnitTests | |
{ | |
public class TransactionServiceTests | |
{ | |
[Fact] | |
public void WhenGetTransactionsRepositoryReturnAFilledDataTable_GetTransactionsServiceReturnsAListOfTransactions() | |
{ | |
// Arrange | |
ITransactionRepository transactionRepositoryMock = Substitute.For<ITransactionRepository>(); | |
var datatable = EmbeddedJsonFileHelper.GetContent<DataTable>(@"Files\datatable.transactions"); | |
var expectedResult = EmbeddedJsonFileHelper.GetContent<List<Transaction>>(@"Files\list.transactions").ToExpectedObject(); | |
transactionRepositoryMock.GetTransactions().Returns(x => datatable); | |
// Act | |
var service = new TransactionService(transactionRepositoryMock); | |
var result = service.GetTransactions(); | |
// Assert | |
expectedResult.ShouldEqual(result); | |
} | |
} | |
} |
Conclusion
As you can see, we can make easy and readable unit tests by using JSON files to populate DataTable with JsonConverter class. Using external files makes this unit test not really isolated, but we can consider it’s acceptable because the data files are included in the test project as “Embedded files”, so the test doesn’t depend on real external resources. ToExpectedObject is also a real “plus”, because this library allow to compare objects by their values instead of their reference, many time I had pain to compare objects property by property….
Hope this article helped you to simplify your unit tests with DataTables. This article is also applicable to DataSets as well