Sometimes you need to write unit tests for a public method which is relying on some other protected methods. For example, lets say we have a business logic class which has a public GetGreatestId
method, which returns the greatest Id from a list of Ids which is provided by a protected virtual GetData
method:
public class MyBusiness
{
public int GetGreatestId()
{
return GetData().Max();
}
protected virtual List<int> GetData()
{
//returns a list of integers from somewhere,
Random r = new Random();
return Enumerable.Range(1, 10).Select(x => r.Next(1000)).ToList();
}
}
Later in this post we will talk about cases that the GetData
method is private
or it’s not virtual. For Now, let’s focus on protected virtual GetData
method.
The question is how to write unit tests for GetGreatestId
method? The main point for writing unit tests, is you need to have an expected list of data returned by GetData
to be able to evaluate the result of GetGreatestId
. To do so, You can use either of following solutions:
- Usnig Inheritance
- Using Mock Frameworks
Using Inheritance
You can derive a new class from MyBusiness
and override GetData
to return an expected list as result and write unit tests base on that:
private class MockedMyBusiness: MyBusiness
{
protected override List<int> GetData()
{
return new List<int>() { 1, 2, 3, 4, 5, 6 };
}
}
Then we can write unit test using the derived class:
[TestMethod]
public void TestMethod2()
{
var business = new MockedMyBusiness();
Assert.AreEqual(6, business.GetGreatestId());
}
Using Mock Frameworks
You can use mock frameworks like Mock
to mock GetData
to return an expected list as result and write unit tests base on that:
//using Moq;
//using Moq.Protected;
[TestMethod]
public void TestMethod1()
{
var mock = new Mock<MyBusiness>();
mock.Protected()
.Setup<List<int>>("GetData")
.Returns(new List<int> { 1, 2, 3, 4, 5, 6 });
var business = mock.Object;
Assert.AreEqual(6, business.GetGreatestId());
}
What should we do if the method is relied on some private or non virtual protected methods?
If you find out you can not write unit test for a public method because it relies on a private or a non virtual protected method, it means there is a design problem with the class which you are going to write unit test for that and it usually means this method belongs to another class or in fact the behavior of that method should be inject-able in the class.
For example, lets say we have a class like following, and we are going to make it unit testable:
public class MyBusiness
{
public int GetGreatestId()
{
return GetData().Max();
}
private List<int> GetData()
{
Random r = new Random();
return Enumerable.Range(1, 10).Select(x => r.Next(1000)).ToList();
}
}
The fastest workaround is making the GetData
method protected virtual
and use one of the solutions which mentioned above.
But as a better fix, to make it unit testable, we should be able to inject GetData
behavior to MyBusiness
. To do so, we can create an interface IMyRepository
having List<int> GetData();
method, and by changing constructor of MyBusiness
class to public MyBusiness(IMyRepository myRepository)
allow injecting IMyRepository
to the business logic class.
By applying these changes our class will have a better design an will be more unit testable and extensible. Then to write unit tests, you can simply inject a known repository which returns an expected set of data and write unit tests based on that.
Here is the redesigned MyBusiness
class:
public interface IMyRepository
{
List<int> GetData();
}
public class MyRepository : IMyRepository
{
public List<int> GetData()
{
Random r = new Random();
return Enumerable.Range(1, 10).Select(x => r.Next(1000)).ToList();
}
}
public class MyBusiness
{
IMyRepository repository;
public MyBusiness(IMyRepository myRepository)
{
repository = myRepository;
}
public int GetGreatestId()
{
return repository.GetData().Max();
}
}
Final Note
Write unit testable code. When you design a class, it’s really important to design it in a way that the class be testable.
Do not avoid unit tests. One of the benefits of writing tests is detecting design problems. The classes should be unit testable and if you find out the class is not testable usually you should look for a extracting some part of the code and injecting it as dependency.
Follow SOLID. Following SOLID principles will help you alot to design better classes.
Hi Reza, so which approach is better for protected methods?
I have a public function which is using over 10 protected methods to set up properties, and also all of them are dependent on DoQuery
Best regards,
Eduardo