Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the crowd is its beautiful and highly expressive specification language. Thanks to its JUnit runner, Spock is compatible with most IDEs, build tools, and continuous integration servers. Spock is inspired from JUnit, jMock, RSpec, Groovy, Scala, Vulcans, and other fascinating life forms.
Attached is the Spock Presentation from the January SunJug meeting.
This is an example of a unit test for some C24 witten in JUnit
JUnit Example
package biz.c24.examples.csv;
import biz.c24.io.api.data.ComplexDataObject;
import biz.c24.io.api.data.Element;
import biz.c24.io.api.data.IOXPathFactory;
import biz.c24.io.api.data.ValidationManager;
import biz.c24.io.api.presentation.Source;
import org.apache.log4j.Appender;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.SimpleLayout;
import org.junit.Assert;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Created by IntelliJ IDEA.
* User: jdavies
* Date: 08/11/2011
* Time: 19:22
*/
public class TransactionsTest {
@Test
public void testValidation() {
ComplexDataObject bean = getTestComplexDataObject();
try {
new ValidationManager().validateByException(bean);
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
@Test
public void testXPath() {
ComplexDataObject bean = getTestComplexDataObject();
try {
Number number = IOXPathFactory.getInstance("sum(//Row1[Currency='GBP']/Amount)").getNumber(bean);
Assert.assertTrue(number.intValue() == 150);
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
@Test
public void testDateValidationFail() {
ComplexDataObject bean = getTestComplexDataObject();
Transactions transactions = (Transactions) bean;
Date firstOfApr = new GregorianCalendar(2011, 3, 1).getTime();
transactions.getRow1()[5].setTransactionDate(firstOfApr);
try {
new ValidationManager().validateByException(bean);
Assert.fail("This should fail validation");
} catch (Exception e) {
Assert.assertTrue(true);
}
}
@Test
public void testRowCountFail() {
ComplexDataObject bean = getTestComplexDataObject();
Transactions transactions = (Transactions) bean;
transactions.getRow2().setTotal(123);
try {
new ValidationManager().validateByException(bean);
Assert.fail("This should fail validation");
} catch (Exception e) {
Assert.assertTrue(true);
}
}
@Test
public void testCardMod10Fail() {
ComplexDataObject bean = getTestComplexDataObject();
Transactions transactions = (Transactions) bean;
transactions.getRow1()[3].setCardNumber("1234-2345-3456-4567");
try {
new ValidationManager().validateByException(bean);
Assert.fail("This should fail validation");
} catch (Exception e) {
Assert.assertTrue(true);
}
}
@Test
public void testCreateNewRows() {
ComplexDataObject bean = getTestComplexDataObject();
Transactions transactions = (Transactions) bean;
for (int i = 0; i < 1000; i++) {
Row1 row = new Row1();
row.setNameElement("John Davies");
row.setTransactionDate(new Date());
row.setAmount(Math.random() * 10000);
row.setCardNumber("1234-2345-3456-4563");
row.setCommission(row.getAmount() * 0.01);
row.setCountry("UK");
row.setCurrency("GBP");
row.setExpiryDate(new GregorianCalendar(2012, 12, 31).getTime());
row.setVendorID(99999L);
transactions.addRow1(row);
}
transactions.getRow2().setTotal(transactions.getRow2().getTotal() + 1000);
try {
new ValidationManager().validateByException(bean);
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
public static boolean isValid(String cardNumber) {
int sum = 0;
int digit = 0;
int addend = 0;
boolean timesTwo = false;
java.lang.StringBuilder stringBuffer = new java.lang.StringBuilder();
char c;
for (int i = 0; i < cardNumber.length(); i++) {
c = cardNumber.charAt
;
if (java.lang.Character.isDigit(c)) {
stringBuffer.append(c);
}
}
java.lang.String digitsOnly = stringBuffer.toString();
for (int i = digitsOnly.length() - 1; i >= 0; i--) {
digit = java.lang.Integer.parseInt(digitsOnly.substring(i, i + 1));
if (timesTwo) {
addend = digit * 2;
if (addend > 9) {
addend -= 9;
}
} else {
addend = digit;
}
sum += addend;
timesTwo = !timesTwo;
}
int modulus = sum % 10;
return modulus == 0;
}
private ComplexDataObject getTestComplexDataObject() {
Element element = TransactionsElement.getInstance();
Appender appender = new ConsoleAppender(new SimpleLayout());
element.getLog().addAppender(appender);
element.getLog().setLevel(Level.OFF);
ComplexDataObject bean = null;
try {
// Parse
Source source = element.getModel().source();
source.setInputStream(new FileInputStream("resources/Transactions-good.txt"));
bean = source.readObject(element);
source.getInputStream().close();
element.getLog().removeAppender(appender);
} catch (IOException e) {
e.printStackTrace();
}
return bean;
}
}
Same Tests - but re-written in Spock
package biz.c24.examples.csv
import spock.lang.*
import biz.c24.io.api.data.ComplexDataObject;
import biz.c24.io.api.data.Element;
import biz.c24.io.api.data.IOXPathFactory;
import biz.c24.io.api.data.ValidationManager;
import biz.c24.io.api.presentation.Source;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.SimpleLayout
import biz.c24.io.api.data.ValidationException;
/**
* Created by IntelliJ IDEA.
* User: john
* Date: 11/9/11
* Time: 11:04 AM
* To change this template use File | Settings | File Templates.
*/
class TransactionsSpec extends Specification {
def "Test Validation"() {
when:
def validationManager = new ValidationManager()
then:
validationManager.validateByException(getTestComplexDataObject())
}
def "Test XPath"() {
when:
Number number = IOXPathFactory.getInstance("sum(//Row1[Currency='GBP']/Amount)").getNumber(getTestComplexDataObject())
then:
number == 150
}
def "Test Date Validation Fail"() {
given:
def bean = getTestComplexDataObject()
when:
bean.getRow1()[5].transactionDate = new Date().parse('yyyy/mm/dd', '2011/4/1') // look ma, duck typing, no cast to keep the compiler happy
new ValidationManager().validateByException(bean);
then:
thrown(Exception)
}
def "Test Row Count Fail"() {
given:
def bean = getTestComplexDataObject().getRow2().setTotal(123)
when:
new ValidationManager().validateByException(bean)
then:
thrown(Exception)
}
def "Test Card Mod 10 Check Fail"() {
given:
def bean = getTestComplexDataObject().getRow1()[3].cardNumber = "1234-2345-3456-4567"
when:
new ValidationManager().validateByException(bean)
then:
thrown(Exception)
}
def "Create new Rows"() {
given:
def bean = getTestComplexDataObject()
(1..1000).each {
def amount = Math.random() * 10000
def commission = amount * 0.01
//the map constructor can be a slow way of object creation, but I wanted to see if it would play nice with the c24 code
def row = new Row1(nameElement: "John Thompson", transactionDate: new Date(), amount: amount,
cardNumber: "1234-2345-3456-4563", commission: commission, country: "US", currency: "USD",
expiryDate: new Date().parse('yyyy/mm/dd', '2012/12/31'), vendorID: 9999L)
bean.addRow1(row)
}
bean.row2.total = bean.row2.total + 1000
def validator = new ValidationManager()
when:
def validate = validator.validateByException(bean)
then:
true // test will fail if validator pops exception
}
def "Create new Rows with validation exception"() {
given:
def bean = getTestComplexDataObject()
(1..100).each {
def amount = Math.random() * 10000
def commission = amount * 0.01
//the map constructor can be a slow way of object creation, but I wanted to see if it would play nice with the c24 code
def row = new Row1(nameElement: "John Thompson", transactionDate: new Date(), amount: amount,
cardNumber: "1234-2345-3456-4563", commission: commission, country: "US", currency: "USD",
expiryDate: new Date().parse('yyyy/mm/dd', '2012/12/31'), vendorID: 9999L)
bean.addRow1(row)
}
bean.row2.total = bean.row2.total + 1000
def validator = new ValidationManager()
when:
validator.validateByException(bean)
then:
thrown(ValidationException)
}
def getTestComplexDataObject() {
Element element = TransactionsElement.getInstance()
def appender = new ConsoleAppender(new SimpleLayout())
element.log.addAppender(appender)
element.log.level = Level.OFF
Source source = element.model.source()
source.inputStream = new FileInputStream("resources/Transactions-good.txt")
ComplexDataObject bean = source.readObject(element)
source.inputStream.close()
element.log.removeAllAppenders()
bean
}
}
Example Spock Spec for a Grails Controller
In this example, Groovy Mock objects and Spock Mock objects are used.
package biz.c24.examples.csv
import spock.lang.*
import biz.c24.io.api.data.ComplexDataObject;
import biz.c24.io.api.data.Element;
import biz.c24.io.api.data.IOXPathFactory;
import biz.c24.io.api.data.ValidationManager;
import biz.c24.io.api.presentation.Source;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.SimpleLayout
import biz.c24.io.api.data.ValidationException;
/**
* Created by IntelliJ IDEA.
* User: john
* Date: 11/9/11
* Time: 11:04 AM
* To change this template use File | Settings | File Templates.
*/
class TransactionsSpec extends Specification {
def "Test Validation"() {
when:
def validationManager = new ValidationManager()
then:
validationManager.validateByException(getTestComplexDataObject())
}
def "Test XPath"() {
when:
Number number = IOXPathFactory.getInstance("sum(//Row1[Currency='GBP']/Amount)").getNumber(getTestComplexDataObject())
then:
number == 150
}
def "Test Date Validation Fail"() {
given:
def bean = getTestComplexDataObject()
when:
bean.getRow1()[5].transactionDate = new Date().parse('yyyy/mm/dd', '2011/4/1') // look ma, duck typing, no cast to keep the compiler happy
new ValidationManager().validateByException(bean);
then:
thrown(Exception)
}
def "Test Row Count Fail"() {
given:
def bean = getTestComplexDataObject().getRow2().setTotal(123)
when:
new ValidationManager().validateByException(bean)
then:
thrown(Exception)
}
def "Test Card Mod 10 Check Fail"() {
given:
def bean = getTestComplexDataObject().getRow1()[3].cardNumber = "1234-2345-3456-4567"
when:
new ValidationManager().validateByException(bean)
then:
thrown(Exception)
}
def "Create new Rows"() {
given:
def bean = getTestComplexDataObject()
(1..1000).each {
def amount = Math.random() * 10000
def commission = amount * 0.01
//the map constructor can be a slow way of object creation, but I wanted to see if it would play nice with the c24 code
def row = new Row1(nameElement: "John Thompson", transactionDate: new Date(), amount: amount,
cardNumber: "1234-2345-3456-4563", commission: commission, country: "US", currency: "USD",
expiryDate: new Date().parse('yyyy/mm/dd', '2012/12/31'), vendorID: 9999L)
bean.addRow1(row)
}
bean.row2.total = bean.row2.total + 1000
def validator = new ValidationManager()
when:
def validate = validator.validateByException(bean)
then:
true // test will fail if validator pops exception
}
def "Create new Rows with validation exception"() {
given:
def bean = getTestComplexDataObject()
(1..100).each {
def amount = Math.random() * 10000
def commission = amount * 0.01
//the map constructor can be a slow way of object creation, but I wanted to see if it would play nice with the c24 code
def row = new Row1(nameElement: "John Thompson", transactionDate: new Date(), amount: amount,
cardNumber: "1234-2345-3456-4563", commission: commission, country: "US", currency: "USD",
expiryDate: new Date().parse('yyyy/mm/dd', '2012/12/31'), vendorID: 9999L)
bean.addRow1(row)
}
bean.row2.total = bean.row2.total + 1000
def validator = new ValidationManager()
when:
validator.validateByException(bean)
then:
thrown(ValidationException)
}
def getTestComplexDataObject() {
Element element = TransactionsElement.getInstance()
def appender = new ConsoleAppender(new SimpleLayout())
element.log.addAppender(appender)
element.log.level = Level.OFF
Source source = element.model.source()
source.inputStream = new FileInputStream("resources/Transactions-good.txt")
ComplexDataObject bean = source.readObject(element)
source.inputStream.close()
element.log.removeAllAppenders()
bean
}
}