Let’s walk through what mocking is and how we can use it to test larger bits of functionality in an automated way and the benefits of code separation.
Integration testing is the level of testing that occurs above the unit test level. At this test level you are enhancing confidence that interactions between the methods will work as expected in all scenarios and can reduce your dependancy on third party applications by mocking.
“Say you have been asked to build a program that will turn potatoes into chips. The units are wash potato, cut potato and fry potato and repeat until all potatoes are all processed. At some point these methods will need glued together to make a single function that creates chips. In order to exercise just the glued together code, we can mock the individual units to return any value we wish without running them for real or even writing them in the first place.”
The beauty of this technique is not only can you test methods that are not written yet, you can also sidestep methods that perhaps you have to pay for, such as google maps or other subscription services.
Interfaces are the accepted method of getting these two benefits and although you can write tests without interfaces you will miss out on other benefits that interfaces allow such as the ability to swap code out (dependancy injection).
“For our second example say we have a coffee making machine. Its units are make coffee and grind beans. The coffee maker should have no concern for what type of beans they are or how they are ground. This allows us to create many different types of coffee. We do however need to know how the coffee machine acts when these methods are brought together. This is the level we want to test.”
The diagram below shows that the interface (ISelectCoffee) has a make coffee and grind beans units. The ICoffeeSelect can have many different concrete implementations. In most programming languages these are materialised as classes or struts. Our example will have WetBeansCoffee(), DryBeansCoffee() and WeakCoffee().
Each of these implementations will have their own programming logic, but they will all be contractually obligated to have a get beans and grind beans method. See the diagram below:
Do we care how we make Coffee? The answer to this question is yes, but not as part of the integration test. All we care about is how these methods interact with each other inside the coffee maker. Therefore, we can comfortably mock these and steer the integration code in any direction we wish.
Mocking our methods is as simple as declaring what method you want to mock and what you want to return when this method is called. Generally, you will want to test all the different scenarios of your larger integration method, including unhappy paths.
The example below simulates a happy path. 5 beans retrieved and then successfully ground.
C#
//Using Moq
[TestMethod]
public void TestTheSweetSweetTasteOfCoffee()
{
Mock<ICoffeeSelect> CoffeeMock = new Mock<ICoffeeSelect>();
CoffeeMock.Setup(test => test.GrindBeans()).Returns(true);
CoffeeMock.Setup(test => test.GetBeans()).Returns(5);
...
}
GoLang
//Using Testify
type MakeCoffeeMock struct {
mock.Mock
}
func TestTheSweetSweetTasteOfCoffee()(t *testing.T) {
makeCoffeeMock := new(MakeCoffeeMock)
makeCoffeeMock.On("GetBeans").Return(5)
makeCoffeeMock.On("GrindBeans").Return(true)
...
}
Java
//Using Mockito
public void TestTheSweetSweetTasteOfCoffee() throws ExecutionControl.NotImplementedException {
ICoffeeSelect coffeeMakerMock = mock(ICoffeeSelect.class);
when(coffeeMakerMock.GetBeans()).thenReturn(5);
when(coffeeMakerMock.GrindBeans()).thenReturn(true);
...
}
Python
# Using unittest.Mock
def test_the_sweat_taste_of_coffee(self):
beans = 5
coffee_select_Mock = MagicMock(AbstractCoffeeSelect)
coffee_select_Mock.get_beans.return_value = beans
coffee_select_Mock.grind_beans.return_value = True
...
We now need to add this Mock into your coffee maker function. We do this in a process called dependency injection using dependancy inversion. This means passing in the methods that we want to call in the coffee maker class as concrete implementation of the ICoffeeSelect interface in the constructor. In this instance however we are not passing in a concrete implementation we are passing in a Mock.
C#
...
//make the coffee, notice the mock here.
ICoffeeMaker coffeeMaker = new CoffeeMaker(CoffeeMock.Object);
//Act
string coffee = coffeeMaker.MakingCoffee();
...
}
GoLang
//make the coffee, notice the mock here.
makeCoffee := CoffeeMaker{coffeeSelect: makeCoffeeMock}
//Act
coffee := makeCoffee.MakingCoffee()
...
}
Java
...
//make the coffee, notice the mock here.
ICoffeeMaker coffeemaker = new CoffeeMaker(coffeeMakerMock);
//Act
String coffee = coffeemaker.MakingCoffee();
...
}
Python
// make the coffee, notice the mock here.
maker = CoffeeMaker(coffee_select_Mock)
// Act
coffee = maker.making_coffee()
...
Next, we just need to test our Coffee Makers result to ensure it has delivered us the overall result that we are expecting. In our case this is a delicious coffee. We do this by counting how many times our Mocks have been called. This count gives us a clear indication of how our code was executed.
C#
...
// Assert
Assert.AreEqual(coffee, $"Coffee has now been brewed with {beans} beans", "Yum we have coffee");
CoffeeMock.Verify(mock => mock.GrindBeans(), Times.Once());
CoffeeMock.Verify(mock => mock.GetBeans(), Times.Once());
...
}
GoLang
...
// Assert
assert.Equal(t, coffee, "Coffee has now been brewed", "Yum we have coffee")
makeCoffeeMock.AssertCalled(t, "GetBeans")
makeCoffeeMock.AssertCalled(t, "GrindBeans")
...
}
Java
...
//make the coffee, notice the mock here.
assertEquals(coffee, "Coffee has now been brewed");
verify(coffeeMakerMock, times(1)).GetBeans();
verify(coffeeMakerMock, times(1)).GrindBeans();
...
}
Python
...
// assert
assert coffee == "Coffee has now been brewed with {} beans".format(beans)
coffee_select_Mock.get_beans.assert_called()
coffee_select_Mock.grind_beans.assert_called()
...
Verify methods are a very important part of iteration tests they allow you to do a host of different things such as:
- Check if internal methods have been called
- Check if internal methods have not been called
- Check what arguments methods where called with
- Check how many times a method was called (think code iterations like loops)
And there you have it, integration testing in a nutshell. You should now understand how to mock minor units of code in order to test the bigger picture.
If you enjoyed this article why not pop over to my unit tests article. This article will cover unit tests in great detail. With more assertions that you can shake a stick at.