I have the following implementation,
public interface IMath {
double Add(double a, double b);
double Subtract(double a, double b);
double Divide(double a, double b);
double Multiply(double a, double b);
double Factorial(int a);
}
public class CMath: IMath {
public double Add(double a, double b) {
return a + b;
}
public double Subtract(double a, double b) {
return a - b;
}
public double Multiply(double a, double b) {
return a * b;
}
public double Divide(double a, double b) {
if (b == 0)
throw new DivideByZeroException();
return a / b;
}
public double Factorial(int a) {
double factorial = 1.0;
for (int i = 1; i <= a; i++)
factorial = Multiply(factorial, i);
return factorial;
}
}
How can I test that Multiply()
is called n times when n's Factorial is calculated?
I'm using NUnit 3 and Moq. Following are the tests that I have already written,
[TestFixture]
public class CMathTests {
CMath mathObj;
[SetUp]
public void Setup() {
mathObj = new CMath();
}
[Test]
public void Add_Numbers9and5_Expected14() {
Assert.AreEqual(14, mathObj.Add(9, 5));
}
[Test]
public void Subtract_5From9_Expected4() {
Assert.AreEqual(4, mathObj.Subtract(9, 5));
}
[Test]
public void Multiply_5by9_Expected45() {
Assert.AreEqual(45, mathObj.Multiply(5, 9));
}
[Test]
public void When80isDividedby16_ResultIs5() {
Assert.AreEqual(5, mathObj.Divide(80, 16));
}
[Test]
public void When5isDividedBy0_ExceptionIsThrown() {
Assert.That(() => mathObj.Divide(1, 0),
Throws.Exception.TypeOf<DivideByZeroException>());
}
[Test]
public void Factorial_Of4_ShouldReturn24() {
Assert.That(mathObj.Factorial(4), Is.EqualTo(24));
}
[Test]
public void Factorial_Of4_CallsMultiply4Times() {
}
}
I'm fairly new to using Moq so I'm not quite getting it at the moment.
Using Verify This is probably the best way to go as Verify is designed for this exact purpose - to verify the number of times a method has been called.
Between - Specifies that a mocked method should be invoked between from and to times. Exactly - Specifies that a mocked method should be invoked exactly times times. Never - Specifies that a mocked method should not be invoked. Once - Specifies that a mocked method should be invoked exactly one time.
CallBase , when initialized during a mock construction, is used to specify whether the base class virtual implementation will be invoked for mocked dependencies if no setup is matched. The default value is false . This is useful when mocking HTML/web controls of the System.
You need to separate mocked part and tested part, cause Moq is about eliminating dependencies, and your CMath class does not have them!
But basically you don't need to test, that Multiply is called 4 times - it's internal implementation. Test results :)
create Separate Factorial class, so that multiply will be in separate interface.
public interface IMath {
double Add(double a, double b);
double Subtract(double a, double b);
double Divide(double a, double b);
double Multiply(double a, double b);
}
public interface IFactorial {
double Factorial(int a, IMath math);
}
And in your test you can create Mock of IMath
[Test]
public void Factorial_Of4_CallsMultiply4Times()
{
var mathMock = new Mock<IMath>();
var factorial = new Factorial();
factorial.Factorial(4, mathMock.Object);
mathMock.Verify(x => x.Multiply(It.IsAny<double>()), Times.Exactly(4));
}
public double Factorial(int a, Func<double,double,double> multiply = null)
{
multiply = multiply ?? CMath.Multiply;
double factorial = 1.0;
for (int i = 1; i <= a; i++)
factorial = multiply(factorial, i);
return factorial;
}
[Test]
public void Factorial_Of4_CallsMultiply4Times()
{
var mathMock = new Mock<IMath>();
var math = new CMath();
math.Factorial(4, mathMock.Object.Multiply);
mathMock.Verify(x => x.Multiply(It.IsAny<double>()), Times.Exactly(4));
}
As has already been said by @aershov, you don't need to test if Multiply
has been called 4 times. It's an implementation detail that you're already testing to an extent in your test Factorial_Of4_ShouldReturn24
. You may want to consider using the TestCase
attribute to allow you to supply a range of inputs to your tests, rather than a single value:
[TestCase(4, 24)]
[TestCase(2, 2)]
[TestCase(1, 1)]
[TestCase(0, 1)]
public void Factorial_OfInput_ShouldReturnExpected(int input, int expectedResult)
{
Assert.That(mathObj.Factorial(input), Is.EqualTo(expectedResult));
}
@aershov covered two design changes that would allow you to mock the interaction you are asking about. A third and arguably the least impactful change would be to make your Multiply
method virtual
. This would allow you to use a partial mock to validate the interaction. The changes would look like this:
Implementation
public class CMath : IMath
{
public virtual double Multiply(double a, double b)
{
return a * b;
}
// ...
Test
Mock<CMath> mockedObj;
CMath mathObj;
[SetUp]
public void Setup()
{
mockedObj = new Mock<CMath>();
mockedObj.CallBase = true;
mathObj = mockedObj.Object;
}
[Test]
public void Factorial_Of4_CallsMultiply4Times()
{
mathObj.Factorial(4);
mockedObj.Verify(x => x.Multiply(It.IsAny<double>(),
It.IsAny<double>()), Times.Exactly(4));
}
I'm not a huge fan of mocking the system under test (it's generally a good sign that you're doing something wrong), however it does allow you to do what you're asking.
Mocks can be very useful, however when you're using them you need to think carefully about what it is you're actually trying to test. Looking at the test above, it could be satisfied with the code below:
public double Factorial(int a)
{
double factorial = 1.0;
for (int i = 1; i <= a; i++)
factorial = Multiply(factorial, a);
return factorial;
}
This code has a critical bug in it, it passes the source argument in to each iteration of the loop, rather than the loop counter. The result is very different, but the number of calls is the same, so the test still passes. This is a good indication that the test isn't actually adding value.
In fact, the test actually causes increased friction in that it's harder to change the implementation of the Factorial
function. Consider an example of 4!. The calculations required are 4*3*2*1
, however the last step the multiplication by 1 is essentially a NOP, since n*1=n
. With this in mind it would be possible to slightly optimize your factorial method to:
public double Factorial(int a)
{
double factorial = 1.0;
for (int i = 2; i <= a; i++)
factorial = Multiply(factorial, i);
return factorial;
}
The input/output tests on the factorial method will continue to work, however the Mocked test that counts the number of calls to Multiply
breaks because only 3 calls are necessary in order to calculate the answer.
Always consider the benefits and costs when deciding to use mock objects.
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