Learn how to divide the application into smaller modules which you can cover by 100% While working for different software houses, on more than one occasion, I had the chance to work on an application that is mainly based on operations. I/O File The biggest challenge the team was facing while working on such kind of applications is that the I/O File operations are so hard to be covered by unit tests, automating Bamboo builds, and many other things. Therefore, once and for all, I decided to come up with the best design I could come up with to overcome these challenges. However, just as a reminder, nothing in software is an absolute truth. For every application, you have to reconsider your design, see where it fits, and where it doesn’t fit, and finally, you need to adapt. Now, as usual, I am going to provide a simple example and walk you through the trip to come up with the best -possible- solution. Example Application Our application is so simple in terms of requirements: The UI is simple as you can see. For simplicity, it is implemented as a Windows Forms project. The Data file the application deals with is a text file with the extension .zzz Every entry in the data file is in the form of {name},{age},{profession} as follows: Mohamed,20,Accountant Patrick,26,Mechanical Engineer Sara,23,Software Tester Note that the entries are separated by the new line character . \r\n Click the button to open a file. The path of the file would appear in the read-only text box above the Browse button. Browse .zzz Click the button so that the application reads that data from the selected file and present them into the at the bottom of the UI. Get All .zzz Reach Text Box Click the button so that the application adds a hardcoded entry to the file and updates the Reach Text Box at the bottom of the UI. Add Click the button so that the application removes the last entry in the file and updates the Reach Text Box at the bottom of the UI. Remove Here are some screenshots to help you get the big picture All the code could be found on so that you can easily follow. this repository Disclaimer Some best practices have been dropped/ignored to drive the main focus to the core purpose and best practices of this article. Some enhancements could be made on the solution but they would be left for you to implement as an exercise. All the code could be found on so that you can easily follow. this repository A sample Data file is also available on the same repository and you can find it . here Bad Code This might be the first thing that comes to your mind when trying to implement this application. using System; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; namespace IOAbstraction { public partial class FrmMain : Form { public FrmMain() { InitializeComponent(); } private void Btn_Browse_Click(object sender, EventArgs e) { if (Ofd_Browse.ShowDialog() == DialogResult.OK) { Txt_Path.Text = Ofd_Browse.FileName; } } private void GetAll() { Rtb_AllResults.Clear(); var lines = File.ReadAllLines(Txt_Path.Text); var builder = new StringBuilder(); foreach (var line in lines) { if (!string.IsNullOrEmpty(line) && line.Contains(",")) { var parts = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var name = parts[0]; var age = parts[1]; var profession = parts[2]; var message = $"Name: {name}, Age: {age}, Profession: {profession}."; builder.AppendLine(message); } } Rtb_AllResults.Text = builder.ToString(); } private void Btn_Get_Click(object sender, EventArgs e) { GetAll(); } private void Btn_Add_Click(object sender, EventArgs e) { var line = Environment.NewLine + "Ahmed,36,Software Engineer"; var text = TrimEndNewLine(File.ReadAllText(Txt_Path.Text)) + line; File.WriteAllText(Txt_Path.Text, text); GetAll(); } private void Btn_Remove_Click(object sender, EventArgs e) { var lines = File.ReadAllLines(Txt_Path.Text); File.WriteAllLines(Txt_Path.Text, lines.Take(lines.Length - 1)); GetAll(); } private string TrimEndNewLine(string str) { var result = str; while (result.EndsWith(Environment.NewLine)) { result = result.Substring(0, result.Length - Environment.NewLine.Length); } return result; } } } What we can notice here is that all the code is in one place: The logic of dealing (opening, reading content, and writing content) with a physical file. The logic of executing UI commands. The logic of formatting data and updating UI. This creates many challenges like: Too many responsibilities for one class. Depending on static classes like . System.IO.File Can’t test without getting into your way. I/O operations logic UI logic Can’t test without getting into your way. UI logic I/O operations logic Will need to always have physical data files to be able to cover the code with unit tests. Even if you succeeded into creating these unit tests and their related physical files, these files would always require maintenance, storage, And they would make planning and implementing and a nightmare. Continuous Integration (CI) Continuous Delivery/Deployment (CD) Therefore, now it is time for a way of fixing this. Good Code The main idea here is to divide the whole solution into smaller parts that we can control and easily cover with unit tests. ISystemFileOperationsManager using System.Collections.Generic; namespace IOAbstraction.SystemFileOperationsManager { public interface ISystemFileOperationsManager { string[] ReadAllLines(string path); string ReadAllText(string path); void WriteAllText(string path, string contents); void WriteAllLines(string path, IEnumerable<string> contents); } } What we can notice here is: This is an interface representing some the I/O File operations we use in our whole solution. The main goal of having this interface is to abstract the dependency we have on the I/O File operations. This abstraction would be so helpful when trying to cover our solution with unit tests as now we have a defined dependency that we can mock. NtfsOperationsManager using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; namespace IOAbstraction.SystemFileOperationsManager { [ExcludeFromCodeCoverage] public class NtfsOperationsManager : ISystemFileOperationsManager { public string[] ReadAllLines(string path) { return File.ReadAllLines(path); } public string ReadAllText(string path) { return File.ReadAllText(path); } public void WriteAllLines(string path, IEnumerable<string> contents) { File.WriteAllLines(path, contents); } public void WriteAllText(string path, string contents) { File.WriteAllText(path, contents); } } } What we can notice here is: This is implementing the interface. ISystemFileOperationsManager It is a wrapper to class. thin System.IO.File That’s why we can easily and safely exclude this class from code coverage as we actually don’t cover .NET built-in classes. IDataFileRepository namespace IOAbstraction.DataFileRepository { public interface IDataFileRepository { string GetAllDataText(); void AddNewDataEntryText(string dataEntryLine); void RemoveLastDataEntryText(); } } What we can notice here is: This is the interface representing the repository manager, which knows about the existence of our Data files and how to write and read text to and from them. This abstraction would be so helpful when trying to cover our solution with unit tests as now we have a defined dependency that we can mock. DataFileRepository using System; using System.Linq; using IOAbstraction.SystemFileOperationsManager; namespace IOAbstraction.DataFileRepository { public class DataFileRepository : IDataFileRepository { private readonly ISystemFileOperationsManager m_SystemFileOperationsManager; private readonly string m_DataFileFullPath; public DataFileRepository(ISystemFileOperationsManager systemFileOperationsManager, string dataFileFullPath) { m_SystemFileOperationsManager = systemFileOperationsManager; m_DataFileFullPath = dataFileFullPath; } public string GetAllDataText() { return m_SystemFileOperationsManager.ReadAllText(m_DataFileFullPath); } public void AddNewDataEntryText(string dataEntryLine) { var line = Environment.NewLine + dataEntryLine; var text = TrimEndNewLine(m_SystemFileOperationsManager.ReadAllText(m_DataFileFullPath)) + line; m_SystemFileOperationsManager.WriteAllText(m_DataFileFullPath, text); } public void RemoveLastDataEntryText() { var lines = m_SystemFileOperationsManager.ReadAllLines(m_DataFileFullPath); m_SystemFileOperationsManager.WriteAllLines(m_DataFileFullPath, lines.Take(lines.Length - 1)); } private string TrimEndNewLine(string str) { var result = str; while (result.EndsWith(Environment.NewLine)) { result = result.Substring(0, result.Length - Environment.NewLine.Length); } return result; } } } What we can notice here is: This is implementing the interface. IDataFileRepository It internally depends on the and uses it to do the I/O File operations. ISystemFileOperationsManager DataEntry namespace IOAbstraction.DataManager.Model { public class DataEntry { public string Name { get; } public string Age { get; } public string Profession { get; } public DataEntry(string name, string age, string profession) { Name = name; Age = age; Profession = profession; } } } What we can notice here is: This is the data object representing our entity that is saved and retrieved to and from our Data files. The property here is implemented as string for simplicity. Age Also, this class should implement to make it easy to apply comparison operations on it. I would leave this part for you to implement. IEquatable<DataEntry> IDataTransformer using System.Collections.Generic; using IOAbstraction.DataManager.Model; namespace IOAbstraction.DataTransformer { public interface IDataTransformer { IEnumerable<DataEntry> CombinedTextToDataEntries(string combinedText); DataEntry TextToDataEntry(string text); string DataEntryToText(DataEntry dataEntry); } } What we can notice here is: This is the interface representing any transformer which has the knowledge of how to convert between text and our . DataEntry This abstraction would be so helpful when trying to cover our solution with unit tests as now we have a defined dependency which we can mock. DataTransformer using System; using System.Collections.Generic; using System.Linq; using IOAbstraction.DataManager.Model; namespace IOAbstraction.DataTransformer { public class DataTransformer : IDataTransformer { public IEnumerable<DataEntry> CombinedTextToDataEntries(string combinedText) { var result = new List<DataEntry>(); var lines = combinedText.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) { if (!string.IsNullOrEmpty(line) && line.Contains(",")) { result.Add(TextToDataEntry(line)); } } return result.Where(r => r != null); } public DataEntry TextToDataEntry(string text) { DataEntry result = null; if (!string.IsNullOrEmpty(text) && text.Contains(",")) { var parts = text.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var name = parts[0]; var age = parts[1]; var profession = parts[2]; result = new DataEntry(name, age, profession); } return result; } public string DataEntryToText(DataEntry dataEntry) { return $"{dataEntry.Name},{dataEntry.Age},{dataEntry.Profession}"; } } } What we can notice here is: This is implementing the interface. IDataTransformer This class encapsulates all the knowledge about our Data transformation between text and . DataEntry IDataManager using System.Collections.Generic; using IOAbstraction.DataManager.Model; namespace IOAbstraction.DataManager { public interface IDataManager { IEnumerable<DataEntry> GetAllData(); void AddNewDataEntry(DataEntry newEntry); void RemoveLastDataEntryText(); } } What we can notice here is: This is the interface representing any manager which is capable of managing our application data without any knowledge about the media where this data is saved in. On this level, there is no reference for . File DataManager using System.Collections.Generic; using IOAbstraction.DataFileRepository; using IOAbstraction.DataManager.Model; using IOAbstraction.DataTransformer; namespace IOAbstraction.DataManager { public class DataManager : IDataManager { private readonly IDataFileRepository m_DataFileRepository; private readonly IDataTransformer m_DataTransformer; public DataManager(IDataFileRepository dataFileRepository, IDataTransformer dataTransformer) { m_DataFileRepository = dataFileRepository; m_DataTransformer = dataTransformer; } public IEnumerable<DataEntry> GetAllData() { return m_DataTransformer.CombinedTextToDataEntries(m_DataFileRepository.GetAllDataText()); } public void AddNewDataEntry(DataEntry newEntry) { m_DataFileRepository.AddNewDataEntryText(m_DataTransformer.DataEntryToText(newEntry)); } public void RemoveLastDataEntryText() { m_DataFileRepository.RemoveLastDataEntryText(); } } } What we can notice here is: This is implementing the interface. IDataManager It internally depends on and uses it to persist and retrieve data in and from Data files. IDataFileRepository Also, it internally depends on and uses it to perform the required conversions. IDataTransformer MainApplication using System.Collections.Generic; using System.Linq; using IOAbstraction.DataManager; using IOAbstraction.DataManager.Model; namespace IOAbstraction.MainApplication { public class MainApplication { private readonly IDataManager m_DataManager; public MainApplication(IDataManager dataManager) { m_DataManager = dataManager; } public IEnumerable<string> GetAllToPresentInUi() { return m_DataManager .GetAllData() .Select(entry => $"Name: {entry.Name}, Age: {entry.Age}, Profession: {entry.Profession}") .ToList(); } public void Add(DataEntry entry) { m_DataManager.AddNewDataEntry(entry); } public void Remove() { m_DataManager.RemoveLastDataEntryText(); } } } What we can notice here is: This is the class that handles the business logic triggered through the application UI. I didn’t abstract this class as an interface but for sure you can do it. I will leave this for you to implement. FrmMain using System; using System.Windows.Forms; using IOAbstraction.DataManager.Model; using IOAbstraction.SystemFileOperationsManager; namespace IOAbstraction { public partial class FrmMain : Form { private MainApplication.MainApplication m_MainApplication; public FrmMain() { InitializeComponent(); } private void Btn_Browse_Click(object sender, EventArgs e) { if (Ofd_Browse.ShowDialog() == DialogResult.OK) { Txt_Path.Text = Ofd_Browse.FileName; var ntfsOperationsManager = new NtfsOperationsManager(); var dataTransformer = new DataTransformer.DataTransformer(); var dataFileRepository = new DataFileRepository.DataFileRepository(ntfsOperationsManager, Txt_Path.Text); var dataManager = new DataManager.DataManager(dataFileRepository, dataTransformer); m_MainApplication = new MainApplication.MainApplication(dataManager); } } private void GetAll() { Rtb_AllResults.Clear(); var lines = m_MainApplication.GetAllToPresentInUi(); Rtb_AllResults.Text = String.Join(Environment.NewLine, lines); } private void Btn_Get_Click(object sender, EventArgs e) { GetAll(); } private void Btn_Add_Click(object sender, EventArgs e) { m_MainApplication.Add(new DataEntry("Ahmed", "36", "Software Engineer")); GetAll(); } private void Btn_Remove_Click(object sender, EventArgs e) { m_MainApplication.Remove(); GetAll(); } } } What we can notice here is: This is the main Form class. It internally depends on class and uses it to execute the main business logic of the application. MainApplication Time For Testing Now, it is time for trying to cover our solution with unit tests. What you would notice here is how easy it would be to cover our whole solution with unit tests. Every module is now designed to do as little as possible and has its own dependencies well defined. So, now let’s create our unit tests project. I am using NUnit and Moq libraries for testing and Mocking. DataFileRepositoryTests using System.Collections.Generic; using System.Linq; using IOAbstraction.SystemFileOperationsManager; using Moq; using NUnit.Framework; namespace IOAbstraction.UnitTests { [TestFixture] public class DataFileRepositoryTests { private const string DummyDataFilePath = "This is a dummy path for testing"; private Mock<ISystemFileOperationsManager> m_SystemFileOperationsManagerMock; private DataFileRepository.DataFileRepository m_Sut; [SetUp] public void SetUp() { m_SystemFileOperationsManagerMock = new Mock<ISystemFileOperationsManager>(); m_Sut = new DataFileRepository.DataFileRepository(m_SystemFileOperationsManagerMock.Object, DummyDataFilePath); } [TearDown] public void TearDown() { m_Sut = null; m_SystemFileOperationsManagerMock = null; } [Test] public void GetAllDataText_ShouldReturnAllData() { // Arrange var text = "This is the sample text"; m_SystemFileOperationsManagerMock .Setup ( m => m.ReadAllText(It.Is<string>(p => p == DummyDataFilePath)) ) .Returns(text) .Verifiable(); // Act var actual = m_Sut.GetAllDataText(); // Assert m_SystemFileOperationsManagerMock .Verify ( m => m.ReadAllText(DummyDataFilePath) ); Assert.AreEqual(text, actual); } [TestCase( "Mohamed,20,Accountant", "Ahmed,36,Software Engineer", "Mohamed,20,Accountant" + "\r\n" + "Ahmed,36,Software Engineer", TestName = "Test Case 01")] [TestCase( "Mohamed,20,Accountant\r\n", "Ahmed,36,Software Engineer", "Mohamed,20,Accountant" + "\r\n" + "Ahmed,36,Software Engineer", TestName = "Test Case 02")] public void AddNewDataEntryText_ShouldAddDataInCorrectFormat(string existingData, string input, string expected) { // Arrange m_SystemFileOperationsManagerMock .Setup ( m => m.ReadAllText(It.Is<string>(p => p == DummyDataFilePath)) ) .Returns(existingData) .Verifiable(); m_SystemFileOperationsManagerMock .Setup ( m => m.WriteAllText ( It.Is<string>(p => p == DummyDataFilePath), It.Is<string>(p => p == expected) ) ) .Verifiable(); // Act m_Sut.AddNewDataEntryText(input); // Assert m_SystemFileOperationsManagerMock .Verify ( m => m.ReadAllText(DummyDataFilePath) ); m_SystemFileOperationsManagerMock .Verify ( m => m.WriteAllText ( DummyDataFilePath, expected ) ); } [Test] public void RemoveLastDataEntryText_ShouldRemoveTheLastLine() { // Arrange var lines = new[] { "Line 1", "Line 2", "Line 3" }; var expected = new[] { "Line 1", "Line 2" }; m_SystemFileOperationsManagerMock .Setup ( m => m.ReadAllLines(It.Is<string>(p => p == DummyDataFilePath)) ) .Returns(lines) .Verifiable(); m_SystemFileOperationsManagerMock .Setup ( m => m.WriteAllLines ( It.Is<string>(p => p == DummyDataFilePath), It.Is<IEnumerable<string>>( p => p.Count() == 2 && p.ElementAt(0) == expected[0] && p.ElementAt(1) == expected[1]) ) ) .Verifiable(); // Act m_Sut.RemoveLastDataEntryText(); // Assert m_SystemFileOperationsManagerMock .Verify ( m => m.ReadAllLines(DummyDataFilePath) ); m_SystemFileOperationsManagerMock .Verify ( m => m.WriteAllLines ( DummyDataFilePath, It.Is<IEnumerable<string>>( p => p.Count() == 2 && p.ElementAt(0) == expected[0] && p.ElementAt(1) == expected[1]) ) ); } } } DataManagerTests using System.Collections.Generic; using System.Linq; using IOAbstraction.DataFileRepository; using IOAbstraction.DataManager.Model; using IOAbstraction.DataTransformer; using Moq; using NUnit.Framework; namespace IOAbstraction.UnitTests { [TestFixture] public class DataManagerTests { private Mock<IDataFileRepository> m_DataFileRepositoryMock; private Mock<IDataTransformer> m_DataTransformerMock; private DataManager.DataManager m_Sut; [SetUp] public void SetUp() { m_DataFileRepositoryMock = new Mock<IDataFileRepository>(); m_DataTransformerMock = new Mock<IDataTransformer>(); m_Sut = new DataManager.DataManager(m_DataFileRepositoryMock.Object, m_DataTransformerMock.Object); } [TearDown] public void TearDown() { m_Sut = null; m_DataFileRepositoryMock = null; m_DataTransformerMock = null; } [Test] public void GetAllData_ShouldGetAllData() { // Arrange var allDataText = "Mohamed,20,Accountant\r\nPatrick,26,Mechanical Engineer"; var allData = new List<DataEntry> { new DataEntry("Mohamed", "20", "Accountant"), new DataEntry("Patrick", "26", "Mechanical Engineer") }; m_DataFileRepositoryMock .Setup ( m => m.GetAllDataText() ) .Returns(allDataText) .Verifiable(); m_DataTransformerMock .Setup ( m => m.CombinedTextToDataEntries( It.Is<string>(p => p == allDataText) ) ) .Returns(allData) .Verifiable(); // Act var actual = m_Sut.GetAllData(); // Assert m_DataFileRepositoryMock .Verify ( m => m.GetAllDataText() ); m_DataTransformerMock .Verify ( m => m.CombinedTextToDataEntries(allDataText) ); Assert.AreEqual(2, actual.Count()); Assert.AreEqual("Mohamed", actual.ElementAt(0).Name); Assert.AreEqual("20", actual.ElementAt(0).Age); Assert.AreEqual("Accountant", actual.ElementAt(0).Profession); Assert.AreEqual("Patrick", actual.ElementAt(1).Name); Assert.AreEqual("26", actual.ElementAt(1).Age); Assert.AreEqual("Mechanical Engineer", actual.ElementAt(1).Profession); } [Test] public void AddNewDataEntry_ShouldAddNewDataEntry() { // Arrange var entry = new DataEntry("Mohamed", "20", "Accountant"); var entryText = "Mohamed,20,Accountant"; var allData = new List<DataEntry> { new DataEntry("Patrick", "26", "Mechanical Engineer") }; m_DataTransformerMock .Setup ( m => m.DataEntryToText( It.Is<DataEntry>(p => p == entry) ) ) .Returns(entryText) .Verifiable(); m_DataFileRepositoryMock .Setup ( m => m.AddNewDataEntryText ( It.Is<string>(p => p == entryText) ) ) .Verifiable(); // Act m_Sut.AddNewDataEntry(entry); // Assert m_DataTransformerMock .Verify ( m => m.DataEntryToText(entry) ); m_DataFileRepositoryMock .Verify ( m => m.AddNewDataEntryText(entryText) ); } [Test] public void RemoveLastDataEntryText_RemoveLastDataEntry() { // Arrange m_DataFileRepositoryMock .Setup ( m => m.RemoveLastDataEntryText() ) .Verifiable(); // Act m_Sut.RemoveLastDataEntryText(); // Assert m_DataFileRepositoryMock .Verify ( m => m.RemoveLastDataEntryText() ); } } } DataTransformerTests using System.Linq; using IOAbstraction.DataManager.Model; using NUnit.Framework; namespace IOAbstraction.UnitTests { [TestFixture] public class DataTransformerTests { private DataTransformer.DataTransformer m_Sut; [SetUp] public void SetUp() { m_Sut = new DataTransformer.DataTransformer(); } [TearDown] public void TearDown() { m_Sut = null; } [Test] public void CombinedTextToDataEntries_ShouldConvertCombinedEntriesTextIntoDataEntries01() { // Arrange var combinedText = "Mohamed,20,Accountant\r\nPatrick,26,Mechanical Engineer"; // Act var actual = m_Sut.CombinedTextToDataEntries(combinedText); // Assert Assert.AreEqual(2, actual.Count()); Assert.AreEqual("Mohamed", actual.ElementAt(0).Name); Assert.AreEqual("20", actual.ElementAt(0).Age); Assert.AreEqual("Accountant", actual.ElementAt(0).Profession); Assert.AreEqual("Patrick", actual.ElementAt(1).Name); Assert.AreEqual("26", actual.ElementAt(1).Age); Assert.AreEqual("Mechanical Engineer", actual.ElementAt(1).Profession); } [Test] public void CombinedTextToDataEntries_ShouldConvertCombinedEntriesTextIntoDataEntries02() { // Arrange var combinedText = "Mohamed,20,Accountant\r\n"; // Act var actual = m_Sut.CombinedTextToDataEntries(combinedText); // Assert Assert.AreEqual(1, actual.Count()); Assert.AreEqual("Mohamed", actual.ElementAt(0).Name); Assert.AreEqual("20", actual.ElementAt(0).Age); Assert.AreEqual("Accountant", actual.ElementAt(0).Profession); } [Test] public void CombinedTextToDataEntries_ShouldConvertCombinedEntriesTextIntoDataEntries03() { // Arrange var combinedText = "Mohamed,20,Accountant\r\n\r\nPatrick,26,Mechanical Engineer"; // Act var actual = m_Sut.CombinedTextToDataEntries(combinedText); // Assert Assert.AreEqual(2, actual.Count()); Assert.AreEqual("Mohamed", actual.ElementAt(0).Name); Assert.AreEqual("20", actual.ElementAt(0).Age); Assert.AreEqual("Accountant", actual.ElementAt(0).Profession); Assert.AreEqual("Patrick", actual.ElementAt(1).Name); Assert.AreEqual("26", actual.ElementAt(1).Age); Assert.AreEqual("Mechanical Engineer", actual.ElementAt(1).Profession); } [Test] public void TextToDataEntry_ShouldConvertEntryTextToDataEntry01() { // Arrange var combinedText = "Mohamed,20,Accountant"; // Act var actual = m_Sut.TextToDataEntry(combinedText); // Assert Assert.AreEqual("Mohamed", actual.Name); Assert.AreEqual("20", actual.Age); Assert.AreEqual("Accountant", actual.Profession); } [Test] public void TextToDataEntry_ShouldConvertEntryTextToDataEntry02() { // Arrange var combinedText = ""; // Act var actual = m_Sut.TextToDataEntry(combinedText); // Assert Assert.IsNull(actual); } [Test] public void TextToDataEntry_ShouldConvertEntryTextToDataEntry03() { // Arrange var combinedText = "Mohamed20Accountant"; // Act var actual = m_Sut.TextToDataEntry(combinedText); // Assert Assert.IsNull(actual); } [Test] public void DataEntryToText_ShouldConvertDataEntryToDataText() { // Arrange var entry = new DataEntry("Mohamed", "20", "Accountant"); var expectedText = "Mohamed,20,Accountant"; // Act var actual = m_Sut.DataEntryToText(entry); // Assert Assert.AreEqual(expectedText, actual); } } } MainApplicationTests using System.Collections.Generic; using IOAbstraction.DataManager; using IOAbstraction.DataManager.Model; using Moq; using NUnit.Framework; namespace IOAbstraction.UnitTests { [TestFixture] public class MainApplicationTests { private Mock<IDataManager> m_DataManagerMock; private MainApplication.MainApplication m_Sut; [SetUp] public void SetUp() { m_DataManagerMock = new Mock<IDataManager>(); m_Sut = new MainApplication.MainApplication(m_DataManagerMock.Object); } [TearDown] public void TearDown() { m_Sut = null; m_DataManagerMock = null; } [Test] public void GetAllToPresentInUi_ShouldGetAllDataIntoTextFormatToPresentInUi() { // Arrange var entries = new List<DataEntry> { new DataEntry("Mohamed", "20", "Accountant"), new DataEntry("Patrick", "26", "Mechanical Engineer") }; var expected = new string[] { "Name: Mohamed, Age: 20, Profession: Accountant", "Name: Patrick, Age: 26, Profession: Mechanical Engineer", }; m_DataManagerMock .Setup ( m => m.GetAllData() ) .Returns(entries) .Verifiable(); // Act var actual = m_Sut.GetAllToPresentInUi(); // Assert CollectionAssert.AreEqual(expected, actual); } [Test] public void Add_ShouldAddEntry() { // Arrange var entry = new DataEntry("Mohamed", "20", "Accountant"); m_DataManagerMock .Setup ( m => m.AddNewDataEntry ( It.Is<DataEntry>(p => p.Name == entry.Name && p.Age == entry.Age && p.Profession == entry.Profession) ) ) .Verifiable(); // Act m_Sut.Add(entry); // Assert m_DataManagerMock .Verify ( m => m.AddNewDataEntry ( It.Is<DataEntry>(p => p.Name == entry.Name && p.Age == entry.Age && p.Profession == entry.Profession) ) ); } [Test] public void Remove_ShouldRemoveLastEntry() { // Arrange m_DataManagerMock .Setup ( m => m.RemoveLastDataEntryText() ) .Verifiable(); // Act m_Sut.Remove(); // Assert m_DataManagerMock .Verify ( m => m.RemoveLastDataEntryText() ); } } } When we run all these unit tests and calculate the test coverage, this would be the result. As you can notice from the screenshot, the only missing part from the coverage is the code itself. Could it be covered? Form Yes, it could be covered as well, however, I will leave this for you to implement. Final Thoughts Now, with the new design, we can easily cover every aspect of our solution with unit tests, and it is so easy to have full control over our application modules. That’s it… Finally, hope you found reading this article as interesting as I found writing it. Also Published Here