Mocking, Stubbing and Spying with Spock:
Parameterized Testing in Spock Framework was explained in detail in this Series of Training Tutorials on Spock.
Mocking and Stubbing are one of the most essential building blocks of extensive Unit tests. Support for mocking and subbing is like the cherry on the cake for a framework.
For existing frameworks like JUnit, JBehave, etc. the support for mocks and stubs does not come out of the box, hence it requires a developer to use third party libraries like Mockito, PowerMock, EasyMock, etc. in order to use them in the unit tests.
In order to understand mocks and stubs and their use cases, you can take a look at our Series of Mockito tutorial.
In this tutorial, we will learn more about the inbuilt Mocking and Stubbing features integrated into the Spock library itself which in turn would enable to use the easier Groovy syntax and thereby reduces the need to add/include any other 3rd party libraries.
You can always include other Mocking frameworks in your tests, as all valid Java code is valid Groovy code as well.
Table of Contents:
Application under Test
Let’s first define a sample Java Application, which we will be testing using mocks and stubs in the Spock framework.
We will be working on a StudentGradeCalculator App which takes the total score from an abstracted database for a given Student ID and has a simple logic of grade assignment depending on the value of the total score. We will use a database interface which has few methods to fetch and update the student scores and grades.
The code for the Application will be available in the last section of this tutorial.
Mocking in Spock
Video Tutorial
In this section, we will see how to instantiate and initialize Mocks in the Spock framework and how to validate interactions on the mock i.e. validation of the calls to the mocks happened as per the expectations of the method under test.
With Mocks, you don’t have to do a lot of setups, but you can validate the interactions that happened with the mock objects supplied to the application under test.
With mocks, you can do things like:
- What arguments the mocks were called with?
- What was the total count of invocations etc?
- Ascertaining the order of mocks.
Let’s see a simple example of the StudentGradeCalculator, where we supply the mocked database implementation object and validate the interactions with the Mock. We’ll try understanding the mocking features with simple examples.
Please note that all the interaction validations should happen in the “then” block by convention.
Below is the code for the method under test (which will be called in the “when:” block)
public String calculateStudentGrade(String studentId) { String grade; // check if grade is already there in database grade = studentDatabase.getStudentGrade(studentId); if(grade!=null && !grade.isEmpty()) { return grade; } List<Float> scoreList = studentDatabase.getStudentScores(studentId); Float totalScore = 0F; if(scoreList !=null) totalScore = scoreList.stream().reduce(0F,(a,b)->a+b); if(totalScore > 90) { grade = "A"; } else if (totalScore > 80) { grade = "B"; } else { grade = "C"; } // update the calculated grade in database studentDatabase.updateStudentGrade(studentId, grade); return grade; }
#1) Validating the interactions with exact arguments: Let’s first validate the interactions with the exactly expected arguments. Here we will expect the mocked methods to be called with the exact arguments (as per the method execution flow).
Here “studentDatabase” is the Mock of a database interface for which we are validating the interactions.
def "illustrate mocks for interaction verification with arguments"() { when: studentReportGenerator.calculateStudentGrade("123"); then: 1*studentDatabase.updateStudentGrade("123","C") 1*studentDatabase.getStudentGrade("123") }
As shown above, we are validating with the exact arguments, so that the mocked implementation must have been called with. Any changes to these arguments will cause the test to fail and the error log shows the appropriate reason.
Let’s try changing the grade in “updateStudentGrade” to “A” instead of the actually called “C” and see what error we get when the test is executed.
Too few invocations for: 1*studentDatabase.updateStudentGrade("123","A") (0 invocations) Unmatched invocations (ordered by similarity): 1 * studentDatabase.updateStudentGrade('123', 'C') 1 * studentDatabase.getStudentScores('123')
It will show an error like “Too few invocations” as it cannot find the Mock invocation with the supplied arguments.
#2) Now let’s see how to validate the Mock interactions without supplying the actual argument values i.e. what we are interested in is just knowing that the mock was invoked on the method but not with what arguments.
These type of requirements are most common while writing unit tests for the actual production code as it’s not always easy to identify the actual arguments which essentially depend on the core business logic of the application under test.
The syntax is simple, you just need to use an underscore “_” for an argument where the actual value is not known.
For Example, to check for any String value, you can just mention “_ as String” in the place of an argument in the test and it should pass for any String value (similarly for other primitive as well as custom data types).
Let’s understand this with an Example
def "illustrate mocks for interaction verification with generic matchers"() { when: studentReportGenerator.calculateStudentGrade("123"); then: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) 1*studentDatabase.getStudentGrade("123") }
An important point to note here is that you can always do mix and match for what arguments are known and what is not known. For instance, in the example below, we are validating the interaction of one mock with the actual arguments and the other with the loose matchers.
#3) Lastly, let’s see a scenario where we can ascertain the order of mock invocation i.e. what order the mocks were called when the test is executed.
It is sometimes essential to validate the flow of events when there are multiple collaborators/mocks involved in the application under test and it’s useful to understand and validate that the methods were called in a pre-determined sequence.
def "illustrate mocks for validating order"() { when: studentReportGenerator.calculateStudentGrade("123"); then: 1*studentDatabase.getStudentGrade("123") then: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) }
This can be achieved by simply using multiple “then:” blocks in the order of Mock sequence expectations. If the mentioned sequence did not meet the actual order of invocation then an error detailing “Wrong invocation order” is thrown.
For instance, if I change the order of the above then statements, the test execution will throw an error as shown below.
Wrong invocation order for: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) (1 invocation) Last invocation: studentDatabase.updateStudentGrade('123', 'C')
Stubbing in Spock
Video Tutorial
We explored all about Mocking, now let’s see how to define Stubs on the mocked objects. Stubbing is nothing but setting up pre-defined or canned responses on the Mock invocations to test the different flows/scenarios of the application under test.
Think of it as programming a mock to return a pre-defined value when it was called. We will continue with the same StudentGradeCalculator app and stub the database interface calls to test different scenarios.
A Stub is like a Mock which in a way emulates the behavior of the real object. You can simply call it as a programmed Mock.
Stubbing Syntax
The syntax for stubbing is 2 right shift operators – i.e. “>>”
In order to set a stub on any call, you can define it as follows :
StubbedObject.StubbedMethod(//argumentList) >> “Stubbed Response”
Let’s now understand the different stubbing scenarios with Examples.
#1) Stubbing with actual parameters: If the arguments are known in advance or if you want to set stub only when the invocation is with specified arguments, this way of specifying stubs can be used.
def "illustrate stubs with exact matchers"() { given: studentDatabase.getStudentScores("123") >> [20F, 30F, 50F] when: def grade = studentReportGenerator.calculateStudentGrade("123") then: grade == "A" }
Here, you can see that the stub has been set with an exact argument i.e. StudentId in this case as “123” (for any other value the stub will not be invoked and there will be a default response returned).
#2) Stubbing with lenient matchers: If the arguments are not known (or are not important), then we can mention them loosely as we did for mocks and the syntax remains same i.e. the underscore “_”.
def "illustrate stubs with loose matchers"() { given: studentDatabase.getStudentScores(_ as String) >> [20F, 30F, 10F] when: def grade = studentReportGenerator.calculateStudentGrade("123") then: grade == "C" }
#3) Let’s see another quick example where we set up stub to throw an exception.
These scenarios are very useful to validate the error handling logic of an application under test (as in the real world, generating all the exceptions actually is not possible but a simple stub could be set up to return whatever exception we want and then assert it in the then block).
def "illustrate stubs with exceptions thrown"() { given: studentDatabase.getStudentScores(_ as String) >> {throw new RuntimeException()} when: studentReportGenerator.calculateStudentGrade("123") then: thrown(RuntimeException.class) }
Spying in Spock
Spies are based on real objects i.e. they need the interface implementation and not the abstract interface itself. Spies are powerful and they can allow you to get real methods called for the application under test and verify what arguments the methods were called for.
Spies also allow defining partial mocks onto the spied object instances. i.e. suppose you want to define the behavior of some methods on the object, then you can and allow the rest to be called as real method calls.
These are typically useful in a situation where there might be some methods of interface that are not implemented and there are few others which are fully functional. Hence, you as a developer can choose to stub the non-implemented ones and call the real implementations of the functional methods.
It should be noted that, for Spied objects, unless stubs are defined, the default behavior will be to call the real implementation. Having said that, spies shouldn’t be frequently called and all the scenario coverage can be achieved using mocks and stubs and a combination of them.
Let’s see few examples using Spies in the Spock framework using the same example of StudentGradeCalculator (We’ve created a real implementation of the StudentDatabase which is an in-memory implementation using HashMap to illustrate calling real methods and returning data. The code will be available in the last section of the tutorial):
#1) Spying using a combination of stub and real method calls
def "illustrate spies"() { given: StudentDatabase spiedStudentDatabase = Spy(StudentDatabase.class) def studentReportGenerator = new StudentReportGenerator(spiedStudentDatabase) when: def grade = studentReportGenerator.calculateStudentGrade("123") then: grade == "A" 1*spiedStudentDatabase.getStudentGrade(_ as String) >> "A" }
The above example illustrates the syntax for creating Spy using the Spock framework. The stub is defined at the declaration time itself.
Also, the spied calls can be verified as illustrated in the then block (with loose argument matchers which can be defined for any specific arguments).
#2) Spying using all real method calls
def "illustrate spies with real method call"() { given: StudentDatabase spiedStudentDatabase = Spy(StudentDatabase.class) def studentReportGenerator = new StudentReportGenerator(spiedStudentDatabase) when: def grade = studentReportGenerator.calculateStudentGrade("123") then: grade == "C" 1*spiedStudentDatabase.getStudentGrade("123") }
In the above example, as we have not mentioned any stubbed behavior, all the calls will go to the real implementation.
Conclusion
In this tutorial, we learned all about the inbuilt techniques to Mock Stub and Spy using the Spock framework. Spock makes it easy by combining these features as a part of the framework itself with a more readable groovy syntax along with the lesser boilerplate code.
Mocks, Stubs, and Spies are used extensively in unit testing for increasing coverage and testing or validating the core business logic of the application under test.
Source code for the Application
StudentReportGenerator.java – this is the method/application under test
package app.studentScores; import java.util.List; public class StudentReportGenerator { public IStudentDatabase studentDatabase; public StudentReportGenerator(IStudentDatabase studentDatabase) { this.studentDatabase = studentDatabase; } public String calculateStudentGrade(String studentId) { String grade; // check if grade is already there in database grade = studentDatabase.getStudentGrade(studentId); if(grade!=null && !grade.isEmpty()) { return grade; } List<Float> scoreList = studentDatabase.getStudentScores(studentId); Float totalScore = 0F; if(scoreList !=null) totalScore = scoreList.stream().reduce(0F,(a,b)->a+b); if(totalScore > 90) { grade = "A"; } else if (totalScore > 80) { grade = "B"; } else { grade = "C"; } // update the calculated grade in database studentDatabase.updateStudentGrade(studentId, grade); return grade; } }
IStudentDatabase.java – Database interface
package app.studentScores; import java.util.List; public interface IStudentDatabase { List<Float> getStudentScores(String studentId); void updateStudentGrade(String studentId, String grade); String getStudentGrade(String studentId); }
StudentDatabase.java – InMemory implementation of the IStudentDatabase.java interface
package app.studentScores; import java.util.*; public class StudentDatabase implements IStudentDatabase { private Map<String, List<Float>> scoreMap; private Map<String, String> gradeMap; public StudentDatabase() { this.scoreMap = new HashMap<>(); this.gradeMap = new HashMap<>(); scoreMap.put("123", Arrays.asList(40F, 30F, 30F)); scoreMap.put("456", Arrays.asList(10F, 10F, 30F)); gradeMap.put("123", "C"); gradeMap.put("456", "A"); } @Override public List<Float> getStudentScores(String studentId) { return scoreMap.get(studentId); } @Override public void updateStudentGrade(String studentId, String grade) { gradeMap.put(studentId,grade); } @Override public String getStudentGrade(String studentId) { return gradeMap.get(studentId); } }
In our upcoming tutorial, we will see how to integrate the Spock framework with other testing frameworks and technologies.