Unit Tests are a way to ensure the logic of your classes are producing the expected results. Those tests can be run from Visual Studio without having to load up the entire application manually then navigating to a specific section and trying to reproduce various scenarios.
There are some widely used naming conventions, for the assembly name it is often used as:
The test files are often named as:
And finally the methods are named as:
So if I have, for example:
- CodingWise.Core
- |- Roman.cs
- |- ConvertFrom
- |- ConvertTo
I will have a test assembly with the following files:
- CodingWise.Core.Tests
- |- RomanTests.cs
- |- ConvertFrom_Decimal1_ReturnsI
- |- ConvertFrom_Decimal9_ReturnsIX
- |- ConvertTo_RomanIV_Returns4
- ...
Unit tests classes and methods must be decorated with attributes. For classes you need to specify that it is a test class with the following attribute:
On methods you need to use the following attribute:
- [TestClass]
- public class RomanConverterTests
- {
- [TestMethod]
- public void ConvertFrom_Decimal1_ReturnsI()
- {
- Assert.AreEqual("I", Roman.ConvertFrom(1));
- }
- }
Assert provides several methods to compare actual results to expected results and if it fails it will throw a notification on the Test Explorer window. Asserts shouldn't be overused on a single method. It
is ok to use more than one Assert but you need to be careful not to test multiple things in the same method.
There are the following four other attributes for methods that are not required but they can be quite useful:
ClassInitialize runs once before all tests.
- ClassInitialize Runs once before all tests.
- ClassCleanUp Runs once after all tests.
- TestInitialize Runs before you run each test.
- TestCleanUp Runs after you run each test.
It is most useful when you want to do a database test. You can create a transaction and then rollback. This way you test the logic and don't pollute/corrupt the database.
- [TestClass]
- public class UserServiceTests
- {
- private ApplicationContext db;
-
- [TestInitialize]
- public void TestInitialize()
- {
- db = new ApplicationContext();
- db.Database.BeginTransaction();
- }
-
- [TestCleanup]
- public void TestCleanUp()
- {
- db.Database.CurrentTransaction.Rollback();
- db.Dispose();
- }
-
- [TestMethod]
- public void Save_AllFields_Completes()
- {
- var userService = new UserService { Context = db };
- var user = new User();
-
- user.Username = "brunolm";
- user.Name = "BrunoLM";
- user.Email = "[email protected]";
-
- userService.Save(user);
- }
- }
There are also “mocks”. The purpose of mocks is to provide external information to your test so you can complete a unit of work. In the following example I'm mocking an:
because it doesn't really matter to the unit of work being tested. The service wasn't mocked because that is under test and what I'm checking here is if the service has the correct logic that is only allowing buyable items to be added to the cart.
The following example uses
Moq.
- public class CartService
- {
- public ApplicationContext Context { get; set; }
-
- public IList<IItem> Items { get; set; }
-
- public CartService()
- {
- Items = new List<IItem>();
- }
-
- public void Add(IItem item)
- {
- if (!item.Buyable)
- throw new ArgumentException("This item can't be bought.", "item");
-
- Items.Add(item);
- }
- }
-
- public interface IItem
- {
- bool Buyable { get; set; }
- }
-
- [TestClass]
- public class CartServiceTests
- {
- [TestMethod]
- [ExpectedException(typeof(ArgumentException))]
- public void Add_NonBuyable_Throws()
- {
- var cartService = new CartService();
-
- var mock = new Mock<IItem>();
- mock.SetupProperty<bool>(item => item.Buyable, false);
-
- cartService.Add(mock.Object);
- }
-
- [TestMethod]
- public void Add_Buyable_Includes()
- {
- var cartService = new CartService();
-
- var mock = new Mock<IItem>();
- mock.SetupProperty<bool>(item => item.Buyable, true);
-
- cartService.Add(mock.Object);
-
- Assert.AreEqual(1, cartService.Items.Count);
- }
- }
Mocks are sometimes overused,
be careful not to fall on “Unit Test Only Tests the Mock” situation. Mocks exist to eliminate external dependencies and allow your code to focus on a single unit of work.
On MVC you will find examples of mocks for controller contexts, HTTP contexts and so on.
So always remember to choose good names, limit the scope of your test and use mocks with responsibility.