Reputation: 2098
I have a recursive method i have tried to simply it for the sake of readability as it is a messy method.
I would like to get 100% coverage of the recursive method. I am struggling to setup the mock that will go through the recursion twice. then exit. I think i would need to use call back and not returns, I am not 100% sure.
[TestMethod]
public void TestRecursiveLoop()
{
var rootList = new List<MyObject> { new MyObject { Id = 999, ParentId = 1000 }, new MyObject { Id = 1000 } };
var myObject = new MyObject { Id = 999 };
var myMock = new Mock<IDBCLASS>();
myMock.Setup(o => o.GETBYType(It.IsAny<Expression<Func<MyObject, bool>>>()))
.Returns((Expression<Func<ShareholderTransaction, bool>> predicate) =>
{
return new List<MyObject>().Where(o => o.ParentId == myObject.Id).ToList();
});
// ?????????? not sure if this is correct
var testRecursion = new TestRecursion(myMock.Object);
testRecursion.Recursive(999, rootList);
}
}
public class TestRecursion
{
private IDBCLASS dbClass;
public TestRecursion(IDBCLASS dbClass)
{
this.dbClass = dbClass;
}
public void Recursive(int id, List<MyObject> list)
{
var parentObject = this.dbClass.GETBYType<MyObject>(x => x.ParentId == id).FirstOrDefault();
if (parentObject == null)
{
return;
}
list.Add(parentObject);
Recursive(parentObject.Id, list);
}
}
public class MyObject
{
public int ParentId { get; set; }
public int Id { get; set; }
}
public class DBCLASS : IDBCLASS
{
public List<T> GETBYType<T>(Expression<Func<T, bool>> p) where T : class
{
// return something from DB;
}
}
public interface IDBCLASS
{
List<T> GETBYType<T>(Expression<Func<T, bool>> p) where T : class;
}
Upvotes: 0
Views: 2058
Reputation: 2509
You have basically two different options:
Either you have a big Arrange section (as in AAA, Arrange Act Assert), or you use a little hack with a minor downside:
Big Arrange approach
as @Brian said in a comment to your question, you could setup GETBYType
to return a different value every time it is called.
The approach he proposes could work, however, a simple, cleaner approach exists: You can use the SetupSequence method.
In essemce, the usage should be something like:
var myMock = new Mock<IDBCLASS>();
myMock.SetupSequence(o => o.GETBYType(It.IsAny<Expression<Func<MyObject, bool>>>()))
.Returns(new MyObject { Id = 999, ParentId = 1000 })
.Returns(new MyObject { Id = 1000 } )
.Returns(null);
The Self hack
There is another approach, which doesn't require you to have a complex arrange for the whole recursion. The idea behind it is to only assert that the recursion is continued correctly.
The problem we would be facing here is that since it's a recursive call, we can't make assertions on the subject under test. In order to work around the problem, we can leverage a little hack. it's not exactly clean, but it will allow you to reduce dramatically the number of tests.
The trick is to inject an object of type TestRecursion
in your TestRecursion
class. At that point, you would then have two constructors: A parameterless constructor, and a constructor accepting an instance of TestRecursion
. The constructor accepting a TestRecursion
object will keep it as a field (let's call it this.self), in order to call the recursive method on that object.
Now, the constructor accepting a TestRecursion
object should NEVER be used by the production code - only by the tests, except in one place: the parameterless constructor will call the constructor expecting a TestRecursion
object, passing this
as an argument.
Other than this exception, the production code will always call the parameterless constructor. Unfortunately, this parameterless constructor will not be easily testable and this is the big downside of the workaround. However, it will now be easy, from the tests, to inject a mocked TestRecursion
and verify that the recursive method was called the correct number of times with the right arguments.
Of course, the implementation of the Recursive
method should be modified a little bit, by replacing the following line
Recursive(parentObject.Id, list);
by
this.self.Recursive(parentObject.Id, list);
At that point it should also be much easier to test the stop condition.
hope this helps.
Upvotes: 3