Groovy language is not as "strict" as Java, I don't think it does not makes a sense to create using Groovy complex production applications, because we can have situation when everything is compiling, but not working. It can we very hard to understand where exactly variable type User was used like it has type Task(for example) :) But for writing test, groovy - is a very good option, because it can make writing of test much easier, with writing less code. In this post I'll show how can we test Java code by BDD methodology, using groovy language and spock library.
Wiki:
def "it should say Hi for regular name"() { given: def person=new TestJavaClass() when : def result=person.sayHi("Huan Sebastyan") then: result!=null result=="Hi, Huan Sebastyan"}
1. Project structure
We have 2 Java classes for testing in our project: JavaTestClass.java, AnotherJavaClass.java, and one groovy test file : GroovyTest.groovy.
2. Main project file(pom.xml)
We have to include some spock-related dependencies into pom file. I commented dependencies which was not used in this project, but can be used in future.
<?xml version="1.0"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demien</groupId> <artifactId>spock</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Test Spock Project</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <build> <plugins> <!-- Mandatory plugins for using Spock --> <plugin> <!-- The gmavenplus plugin is used to compile Groovy code. To learn more about this plugin, visit https://github.com/groovy/GMavenPlus/wiki --> <groupId>org.codehaus.gmavenplus</groupId> <artifactId>gmavenplus-plugin</artifactId> <version>1.4</version> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> <!-- Optional plugins for using Spock --> <!-- Only required if names of spec classes don't match default Surefire patterns (`*Test` etc.) --> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.6</version> <configuration> <useFile>false</useFile> <includes> <include>**/*Spec.java</include> </includes> </configuration> </plugin> <!-- Only required for spock-example build --> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.5</version> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> </build> <dependencies> <!-- Mandatory dependencies for using Spock --> <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <version>1.0-groovy-2.4</version> <scope>test</scope> </dependency> <!-- Optional dependencies for using Spock --> <dependency> <!-- use a specific Groovy version rather than the one specified by spock-core --> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.4.1</version> </dependency> <!-- enables mocking of classes (in addition to interfaces) --> <dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>3.1</version> <scope>test</scope> </dependency> <!-- enables mocking of classes without default constructor (together with CGLIB) <dependency> <groupId>org.objenesis</groupId> <artifactId>objenesis</artifactId> <version>2.1</version> <scope>test</scope> </dependency> --> <!-- only required if Hamcrest matchers are used <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> <version>1.3</version> <scope>test</scope> </dependency> --> <!-- Dependencies used by examples in this project (not required for using Spock) <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.185</version> </dependency> --> </dependencies> <!-- Only required if a snapshot version of Spock is used <repositories> <repository> <id>spock-snapshots</id> <url>https://oss.sonatype.org/content/repositories/snapshots/</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> --> <!-- Only required in spock-example build <distributionManagement> <repository> <id>foo</id> <url>file:///fake.repository.to.make.maven.happy</url> </repository> <snapshotRepository> <id>bar</id> <url>file:///fake.repository.to.make.maven.happy</url> </snapshotRepository> </distributionManagement> --> </project>
3. JavaTestClass
It's a very simple class, it just has 2 methods : sayHi and getHistory(returns List with history of invocatons).
package com.demien.spock; import java.util.ArrayList; import java.util.List; public class TestJavaClass { private List<String> history=new ArrayList<String>(); public void checkName(String name) { if (name==null) { throw new IllegalArgumentException("name is null!"); } } public String sayHi(String name) { checkName(name); history.add(name); return "Hi, "+name; } public List<String> getHistoryItems() { return history; } }
4.AnotherJavaClass
Just another simple class which use JavaTestClass for method intruduceYourself. Also it use getHistory method for transforming result from JavaTestClass to String(from List).
package com.demien.spock; public class AnotherJavaClass { TestJavaClass testJavaClass; public void setTestJavaClass(TestJavaClass testJavaClass) { this.testJavaClass = testJavaClass; } public String introduceYourself() { return testJavaClass.sayHi("I'm AnotherJavaClass"); } public String getHistory() { StringBuilder result=new StringBuilder(); for (String s:testJavaClass.getHistoryItems()) { if (result.length()>0) { result.append(","); } result.append(s); } return result.toString(); } }
5. Groovy test
By comments in code I tried to explain everything what is going on. I believe, it's easy to understand.
package com.demien.spock import spock.lang.Specification; class GroovyTest extends Specification { def "it should say Hi for regular name"() { given: def person=new TestJavaClass() when : def result=person.sayHi("Huan Sebastyan") then: result!=null result=="Hi, Huan Sebastyan" } def "it should thrown an error if name is null"() { given: def person=new TestJavaClass() when : def result=person.sayHi(null) then: thrown(IllegalArgumentException) } // mock - is a "empty" class: all methods are returning null. def "check method invocation count"() { given: TestJavaClass mock=Mock() AnotherJavaClass anotherJavaClass=new AnotherJavaClass(testJavaClass: mock) when: mock.sayHi("Test Name") // first invocation anotherJavaClass.introduceYourself() // second invocation then: 2*mock.sayHi(_) } // you have to re-define each method you want to use for mock def "Mocked class used in another class should return mocked value"() { setup : TestJavaClass mock=Mock() mock.sayHi(_)>>"EMPTY" AnotherJavaClass anotherJavaClass=new AnotherJavaClass(testJavaClass: mock) when: String result=anotherJavaClass.introduceYourself() then: result=="EMPTY" } def "Mocked class should return proper mocked history"() { setup : TestJavaClass mock=Mock() mock.getHistoryItems()>>["one", "two", "three"] AnotherJavaClass anotherJavaClass=new AnotherJavaClass(testJavaClass: mock) when: String result=anotherJavaClass.getHistory() then: result=="one,two,three" } // stubbed class - "empty" class like mock, but methods are not returning values at all // so, like in mocks, you have to re-define each method you want to use def "stubbed class used in another class"() { setup: TestJavaClass stub=Stub() stub.sayHi(_)>>{String name-> "Hello, "+name } AnotherJavaClass anotherJavaClass=new AnotherJavaClass(testJavaClass: stub) when: String result=anotherJavaClass.introduceYourself() then: result=="Hello, I'm AnotherJavaClass" } // spy - is a "wrapper" - 100% working copy of class // but you can re-define methods if you want def "spy test"() { setup: TestJavaClass spy=Spy() spy.checkName(null)>>{} // now calling this method with (null) will not cause exeption AnotherJavaClass anotherJavaClass=new AnotherJavaClass(testJavaClass: spy) when: String resultFromClass=anotherJavaClass.introduceYourself() String resultFromSpy=spy.sayHi(null) then: resultFromClass=="Hi, I'm AnotherJavaClass" resultFromSpy=="Hi, null" // exception was not thrown } }
6. Conclusion.
I think groovy is becoming more and more popular for writing test, and spock framework will help it with that! Source code can be downloaded from here.