Here is a very simplistic example of what I'm trying to do:
public class Bar
{
public void SomeMethod(string param)
{
//whatever
}
}
public interface IBarRepository
{
List<Bar> GetBarsFromStore();
}
public class FooService
{
private readonly IBarRepository _barRepository;
public FooService(IBarRepository barRepository)
{
_barRepository = barRepository;
}
public List<Bar> GetBars()
{
var bars = _barRepository.GetBarsFromStore();
foreach (var bar in bars)
{
bar.SomeMethod("someValue");
}
return bars;
}
}
In my test, I'm mocking IBarRepository to return a concrete List defined in the unit test and passing that mocked repository instance to the FooService constructor.
I want to verify in the FooService method GetBars that SomeMethod was called for each of the Bars returned from the repository. I'm using Moq. Is there any way to do this without mocking the list of Bars returned (if even possible) and not having to put some hacky flag in Bar (yuck) ?.
I'm following an example from a DDD book, but I'm starting to think it smells because I'm challenged in testing the implementation....
Revised... this passes:
public class Bar
{
public virtual void SomeMethod(string param)
{
//whatever
}
}
public interface IBarRepository
{
List<Bar> GetBarsFromStore();
}
public class FooService
{
private readonly IBarRepository _barRepository;
public FooService(IBarRepository barRepository)
{
_barRepository = barRepository;
}
public List<Bar> GetBars()
{
var bars = _barRepository.GetBarsFromStore();
foreach (var bar in bars)
{
bar.SomeMethod("someValue");
}
return bars;
}
}
[TestMethod]
public void Verify_All_Bars_Called()
{
var myBarStub = new Mock<Bar>();
var mySecondBarStub = new Mock<Bar>();
var myBarList = new List<Bar>() { myBarStub.Object, mySecondBarStub.Object };
var myStub = new Mock<IBarRepository>();
myStub.Setup(repos => repos.GetBarsFromStore()).Returns(myBarList);
var myService = new FooService(myStub.Object);
myService.GetBars();
myBarStub.Verify(bar => bar.SomeMethod(It.IsAny<string>()), Times.Once());
mySecondBarStub.Verify(bar => bar.SomeMethod(It.IsAny<string>()), Times.Once());
}
Note the slight change to class Bar (SomeMethod() is virtual). A change, but not one involving a flag... :)
Now, in terms of broader design, there is some mutation going on with your bar (whatever "SomeMethod()" actually does). The best thing to do would probably be to verify that this mutation happened on each Bar returned from FooService.GetBars(). That is, setup your repository stub to return some bars, and then verify that whatever mutation is performed by SomeMethod() has taken place. After all, you control the Bars that will be returned, so you can setup their pre-SomeMethod() state, and then inspect their post-SomeMethod() state.
If I was writing these classes with unit testing in mind, I would likely either have the class Bar
implement an interface IBar
and use that interface in my service, or make SomeMethod
virtual in Bar
.
Ideally like this:
public interface IBar
{
void SomeMethod(string param);
}
public class Bar : IBar
{
public void SomeMethod(string param) {}
}
public interface IBarRepository
{
List<IBar> GetBarsFromStore();
}
public class FooService
{
private readonly IBarRepository _barRepository;
public FooService(IBarRepository barRepository)
{
_barRepository = barRepository;
}
public List<IBar> GetBars()
{
var bars = _barRepository.GetBarsFromStore();
foreach (var bar in bars)
{
bar.SomeMethod("someValue");
}
return bars;
}
}
Then my unit test would look as follows:
[Test]
public void TestSomeMethodCalledForEachBar()
{
// Setup
var barMocks = new Mock<IBar>[] { new Mock<IBar>(), new Mock<IBar>() };
var barObjects = barMocks.Select(m => m.Object);
var repoList = new List<IBar>(barsObjects);
var repositoryMock = new Mock<IBarRepository>();
repositoryMock.Setup(r => r.GetBarsFromStore()).Returns(repoList);
// Execute
var service = new FooService(repositoryMock.Object);
service.GetBars();
// Assert
foreach(var barMock in barMocks)
barMock.Verify(b => b.SomeMethod("someValue"));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With