Spock vs Junit with Mockito
Introduction
Spock and JUnit are frameworks for unit testing of Java applications. Mockito is a well – known and stable library of mocking extensions for JUnit to write tests in Java language. On the other hand Spock is a complete testing framework on top of JUnit that is designed for testing Java applications, but tests are written in Groovy. Mockito has much longer history while Spock’s stable version was released in March 2015. So Spock is relatively young, but has bright future. I will try to compare JUnit with Spock in this article, but I cannot promise that I will be a neutral judge here because I am an advocate of Spock.
All examples shown in this article are available on github: https://github.com/wmaziarz/jlabs-spock-mockito
Basic test examples
Both unit tests should test the class DefaultMassMailSender and its public method that should select users with user names starting with a given string and send them email messages with provided subject and content.
JUnit with Mockito:
/**
* should send emails to matching users and return a list of email addresses that the email has been sent to
*/
@Test
public void testSendingEmailsToMatchingUsers() {
//given:
User user1 = new User();
user1.setId(1L);
user1.setUserName("test user one");
user1.setEmail("test@user.one");
User user2 = new User();
user2.setId(2L);
user2.setUserName("IGNORED test user two");
user2.setEmail("test@user.two");
User user3 = new User();
user3.setId(3L);
user3.setUserName("THIRD test user");
user3.setEmail("test@user.three");
User user4 = new User();
user4.setId(4L);
user4.setUserName("testing user four");
user4.setEmail("test@user.four");
String subject = "some test email subject";
String content = "some content of the email";
when(userRepository.findAll()).thenReturn(Arrays.asList(user1, user2, user3, user4));
//when:
List<String> result = underTest.sendEmailsToUsers(subject, content, "test");
//then:
Assert.assertEquals(Arrays.asList("test@user.one", "test@user.four"), result);
verify(userRepository).findAll();
}
Spock:
def "should send emails to matching users and return a list of email addresses that the email has been sent to"() {
given:
def user1 = new User(id: 1L, userName: 'test user one', email: 'test@user.one')
def user2 = new User(id: 2L, userName: 'IGNORED test user two', email: 'test@user.two')
def user3 = new User(id: 3L, userName: 'THIRD test user', email: 'test@user.three')
def user4 = new User(id: 4L, userName: 'testing user four', email: 'test@user.four')
def subject = 'some test email subject'
def content = 'some content of the email'
when:
def result = underTest.sendEmailsToUsers(subject, content, 'test')
then: "get users from repository and send email to each matching user"
1 * userRepository.findAll() >> [user1, user2, user3, user4]
1 * emailSender.sendEmail(subject, content, 'test@user.one')
1 * emailSender.sendEmail(subject, content, 'test@user.four')
result == ['test@user.one', 'test@user.four']
}
Both tests do the same. But what is the main difference between those unit tests source codes?
- Spock test is shorter: it is an undeniable fact. Spock – 21 lines of code vs. Mockito – 36 lines of code. In JUnit it is necessary to call constructors, setters and building collections in a Java way. JUnit test code has to be very verbose because it is written in a pure Java language. While in Spock you don’t have to waste a time for writing all this stuff which pollutes the code and make it less readable. Writing a Spock test you may focus on the test itself and all these auxiliary parts of code are limited to minimum.
- Spock is a framework which includes: unit testing, integration testing, mocking and stubbing. Other than that it is a specification of the application. JUnit is only a framework for unit testing and adding a Mockito (or other mocking library) is needed if mocking ability is required.
- Spock code is more elegant. Moreover it is compact and much less verbose than Mockito’s. It is a matter of taste, however I believe that many programmers would agree with this opinion.
- JUnit test is written in pure Java which is rather known to Java programmers. Spock test is written in Groovy which might make some Java programmers unhappy. But don’t be afraid – Groovy is not so bad. Moreover Spock’s official documentation is comprehensive and helpful. There are also more and more other resources in the Internet to learn Spock and Groovy from.
- Spock code is more readable and self – explaining. Starting from a method name which in JUnit must follow some strict naming conventions and if you want to describe the test you have to add some comment. In contradiction a method name in Spock could be whatever string you wish. Test parts are clearly separated in Spock by labels ‘given’, ‘when’, ‘then’ (there are also others available) while in a case of JUnit test there is no such separation unless programmer adds some comments. The labels in Spock might be used in test reports generated by some dedicated plugins to make it more valuable. Whereas comments in JUnit code is helpful only for programmers working on this code.
- Spock test extends spock.lang.Specification class with some reason. It is just a form of documentation and specification of the code being under test. Spock’s syntax, method names, labels – all this items help to achieve the goal which is: a test being a specification of the code indeed. Have you ever tried to read any JUnit test to understand of how the application under test should work?
- Spock test may be even understood not only by programmers who often live in their own galaxy and are considered as weird by other people J. Try to show the Spock Specification to some Business Analysts and ask him or her to read it. And try to do the same with JUnit test…
Data driven testing
The real power of Spock appears if you want to write some more complex test and exercise the same test many times with different input data and expected results.
Such a test may be written in JUnit with use of Parametrized runner. And in Spock you just need to define data table in ‘where’ section. Please look at the examples below.
JUnit with Mockito:
@RunWith(Parameterized.class)
public class DefaultMassMailSenderParametrizedMockitoTest {
private UserRepository userRepository;
private EmailSender emailSender;
private DefaultMassMailSender underTest;
private String userName;
private List<String> expectedEmails;
public DefaultMassMailSenderParametrizedMockitoTest(String userName, List<String> expectedEmails) {
this.userName = userName;
this.expectedEmails = expectedEmails;
}
@Before
public void setUp() {
userRepository = mock(UserRepository.class);
emailSender = mock(EmailSender.class);
underTest = new DefaultMassMailSender();
underTest.setUserRepository(userRepository);
underTest.setEmailSender(emailSender);
}
@Parameterized.Parameters
public static List<Object[]> expectedUserEmails() {
return Arrays.asList(new Object[][] {
{"test", Arrays.asList("test@user.one", "test@user.four")},
{"testing", Collections.singletonList("test@user.four")},
{"z", Collections.emptyList()},
{"third", Collections.singletonList("test@user.three")}
});
}
/**
* should send emails to matching users and return a list of email addresses that the email has been sent to - parametrized test
*/
@Test
public void testSendingEmailsToMatchingUsers() {
//given:
User user1 = new User();
user1.setId(1L);
user1.setUserName("test user one");
user1.setEmail("test@user.one");
User user2 = new User();
user2.setId(2L);
user2.setUserName("IGNORED test user two");
user2.setEmail("test@user.two");
User user3 = new User();
user3.setId(3L);
user3.setUserName("THIRD test user");
user3.setEmail("test@user.three");
User user4 = new User();
user4.setId(4L);
user4.setUserName("testing user four");
user4.setEmail("test@user.four");
String subject = "some test email subject";
String content = "some content of the email";
when(userRepository.findAll()).thenReturn(Arrays.asList(user1, user2, user3, user4));
//when:
List<String> result = underTest.sendEmailsToUsers(subject, content, userName);
//then:
Assert.assertEquals(expectedEmails, result);
verify(userRepository).findAll();
}
Spock:
def "should send emails to matching users and return a list of email addresses that the email has been sent to - test with data table"() {
given:
def user1 = new User(id: 1L, userName: 'test user one', email: 'test@user.one')
def user2 = new User(id: 2L, userName: 'IGNORED test user two', email: 'test@user.two')
def user3 = new User(id: 3L, userName: 'THIRD test user', email: 'test@user.three')
def user4 = new User(id: 4L, userName: 'testing user four', email: 'test@user.four')
def subject = 'some test email subject'
def content = 'some content of the email'
when:
def result = underTest.sendEmailsToUsers(subject, content, userNameLike)
then:
1 * userRepository.findAll() >> [user1, user2, user3, user4]
_ * emailSender.sendEmail(subject, content, _ as String)
result == expectedEmailList
where:
userNameLike | expectedEmailList
'test' | ['test@user.one', 'test@user.four']
'testing' | ['test@user.four']
'z' | []
'third' | ['test@user.three']
}
A difference in a source codes size, complexity, verbosity and elegance is noticeable. Isn’t it? In JUnit it is necessary to create separate JUnit class annotated with @RunWith(Parametrized.class), create this two – dimensional array in a dedicated method annotated with @Parametrized.Parameters. In Spock there is only ‘where’ section with easy to read table added. You cannot easily add two parametrized tests in a single JUnit test class. In Spock it is very easy thanks to data table placed in an optional ‘where’ section of a single test method which has not any impact on other test methods in the same class.
Summary
The right choice of a testing framework for your application is a matter of many factors. In some cases options are limited and sometimes even there is no choice. If you are about to start a new project and you are free to decide of what technology to use consider Spock as a testing framework. If your project is mature you can also add Spock next to existing JUnit tests. If your project is mature and has little test code coverage or has not any tests at all then nothing should stop you before adding Spock to it.
With Spock you will write unit and integration tests quickly and they will look like much more elegant and give more value than old good JUnit tests.