Upload
proidea
View
71
Download
2
Embed Size (px)
Citation preview
SpockDominik Przybysz
https://github.com/alien11689/spock-show
[email protected] @alien11689 http://przybyszd.blogspot.com
Context
Person.groovy@Canonicalclass Person { String firstName String lastName Integer age
boolean isAdult() { age >= 18 }}
PersonValidator.java Part. 1@Componentpublic class PersonValidator { public void validatePerson(Person person) { String firstName = person.getFirstName(); if (firstName == null || firstName.length() == 0) { throw new PersonValidationException("First name must be given"); } String lastName = person.getLastName(); if (lastName == null || lastName.length() == 0) { throw new PersonValidationException("Last name must be given"); }
PersonValidator.java Part. 2 Integer age = person.getAge(); if (age == null){ throw new PersonValidationException("Age must be given"); } if( age < 0) { throw new PersonValidationException("Age cannot be negative"); } }}
PersonValidationException.java
public class PersonValidationException extends RuntimeException { public PersonValidationException(String message) { super(message); }}
PersonDao.groovy Part. 1
@Componentclass PersonDao { final JdbcTemplate jdbcTemplate
@Autowired PersonDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate }
PersonDao.groovy Part. 2void persist(List<Person> persons) { persons.each { persist(it) } }
@Transactional void persist(Person person) { jdbcTemplate.execute("Insert into person (first_name, last_name, age) values ('${person.firstName}', '${person.lastName}', ${person.age})") }
PersonDao.groovy Part. 3 List<Person> findByLastName(String lastName) { jdbcTemplate.queryForList("select first_name, last_name, age from person where last_name = ?", [lastName] as Object[]) .collect({Map row -> new Person(row.first_name, row.last_name, row.age) }) }
void close() { println "Closing person dao" }}
PersonController.groovy Part. 1@Componentclass PersonController { final PersonValidator personValidator final PersonDao personDao
@Autowired PersonController(PersonValidator personValidator, PersonDao personDao) { this.personValidator = personValidator this.personDao = personDao }
PersonController.groovy Part. 2
void addPerson(Person person) { personValidator.validatePerson(person) personDao.persist(person) }}
PersonContextConfiguration.groovy Part. 1
@Configuration@ComponentScan("com.blogspot.przybyszd.spock")class PersonContextConfiguration { @Bean JdbcTemplate getJdbcTemplate(DataSource dataSource){ return new JdbcTemplate(dataSource) }
PersonContextConfiguration.groovy Part. 2 @Bean DataSource getDataSource() { BasicDataSource basicDataSource = new
BasicDataSource() basicDataSource .setDriverClassName("org.h2.Driver") basicDataSource.setUrl("jdbc:h2:mem:personDB;DB_CLOSE_DELAY=1000;INIT=runscript from 'classpath:db/person.sql';") basicDataSource.setUsername("sa") basicDataSource.setPassword("") return basicDataSource }}
Introduction
Dependenciescompile 'org.codehaus.groovy:groovy-all:2.4.0'compile 'org.springframework:spring-jdbc:4.0.5.RELEASE'compile 'org.springframework:spring-beans:4.0.5.RELEASE'compile 'org.springframework:spring-context:4.0.5.RELEASE'compile 'commons-dbcp:commons-dbcp:1.4'compile 'com.h2database:h2:1.4.178'
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'testCompile 'org.spockframework:spock-spring:1.0-groovy-2.4'testCompile 'org.springframework:spring-test:4.0.5.RELEASE'testCompile 'cglib:cglib-nodep:3.1'testCompile 'org.objenesis:objenesis:2.1'
when-then blocks
class PersonTest extends Specification { def "should set first name from constructor"() { when: Person person = new Person(firstName: "Bob") then: person.firstName == "Bob" }}
Test failed output
person.firstName == "Bob"| | || Bb false| 1 difference (66% similarity)| B(-)b| B(o)bcom.blogspot.przybyszd.spock.dto.Person(Bb, null, null)
Block with description
def "should set first name from constructor 2"() { when: "person with set first name" Person person = new Person(firstName: "Bob") then: "person has first name" person.firstName == "Bob"}
Given block
def "should set first name from setter"() { given: Person person = new Person(firstName: "Bob") when: person.firstName = 'Tom' then: person.firstName == "Tom"}
Multiple asserts
def "should set person data from constructor"() { when: Person person = new Person("Bob", "Smith", 15) then: person.firstName == "Bob" person.lastName == "Smith" person.age == 15}
Multiple when then
def "should set first name from constructor and change with setter"() { when: Person person = new Person(firstName: "Bob") then: person.firstName == "Bob" when: person.firstName = "Tom" then: person.firstName == "Tom"}
And block
def "should set first name and last name"() { when: Person person = new Person(firstName: "Bob", lastName: "Smith") then: person.firstName == "Bob" and: person.lastName == "Smith"}
Expect block
def "should compare person with equals"() { expect: new Person("Bob", "Smith", 15) == new Person("Bob", "Smith", 15)}
Lifecycle
Test fields
class LifecycleSpockTest extends Specification {
@Shared StringWriter writer
Person person
}
Setup specification
def setupSpec() { println "In setup spec" writer = new StringWriter()}
Setup each test
def setup() { println "In setup" person = new Person(firstName: "Tom", lastName: "Smith", age: 21)}
Cleanup each test
def cleanup() { println "In cleanup" person = null}
Cleanup specification
def cleanupSpec() { println "In cleanup spec" writer.close()}
Setup and clenup blocks
def "should check firstName"() { setup: println "setup in test" println "should check firstName" expect: person.firstName == "Tom" cleanup: println "Cleanup after test"}
Statements without block
def "should check lastName"() { println "should check lastName" expect: person.lastName == "Smith"}
Parameters
Parameters in table@Unrolldef "should set person data"() { when: Person person = new Person(lastName: lastName, firstName: firstName, age: age) then: person.firstName == firstName person.lastName == lastName person.age == age where: lastName | firstName | age "Smith" | "John" | 25 "Kowalski" | "Jan" | 24}
Parameters in method signature@Unrolldef "should set person data 2"(String firstName, String lastName, int age) { // … where: lastName | firstName | age "Smith" | "John" | 25 "Kowalski" | "Jan" | 24}
Parameters in method name@Unrolldef "should set person with #lastName, #firstName and #age"() { // … where: lastName | firstName | age "Smith" | "John" | 25 "Kowalski" | "Jan" | 24}
Call parameterless method in test name Part 1
@Unroll("should set person with #lastName.length(), #firstName.toUpperCase() and #age")
Call parameterless method in test name Part 2
@Unroll("should set person with #lastName.length(), #firstName.toUpperCase() and #age when last name starts with #firstLetter")def "should set person with lastName, firstName and age 3"() { //… where: lastName | firstName | age "Smith" | "John" | 25 "Kowalski" | "Jan" | 24
firstLetter = lastName.charAt(0)}
Separeted table
@Unrolldef "should check if person is adult with table"() { expect: new Person(age: age).isAdult() == adult where: age || adult 17 || false 18 || true 19 || true}
Parameters from list
@Unrolldef "should check if person is adult with list"() { expect: new Person(age: age).isAdult() == adult ageSquare == age * age where: age << [17, 18, 19] adult << [false, true, true] ageSquare = age * age}
Parameters from list of list
@Unrolldef "should check if person is adult with list 2"() { expect: new Person(age: age).isAdult() == adult where: [age, adult] << [[17,false], [18,true], [19, true]]}
One paramter table
@Unrolldef "should set first name"() { when: Person person = new Person(firstName: firstName) then: person.firstName == firstName where: firstName | _ "John" | _ "Jan" | _}
Parameters from db - setupstatic Sql sql = Sql.newInstance("jdbc:h2:mem:", "sa", "", "org.h2.Driver")
def setupSpec() { sql.execute("""DROP TABLE IF EXISTS person; CREATE TABLE person ( first_name VARCHAR(256) NOT NULL, last_name VARCHAR(256) NOT NULL, age INT NOT NULL );""") sql.executeInsert("""INSERT INTO person (first_name, last_name, age) VALUES ('Tom', 'Smith', 24), ('Jan', 'Kowalski', 30);""")}
Parameters from db - cleanup
def cleanupSpec() { sql.close()}
All parameters from db@Unrolldef "should set person data with #lastName, #firstName and #age"() { // … where: [firstName, lastName, age] << sql.rows("SELECT * FROM person;")}
All parameters from db by name@Unrolldef "should set person data with #lastName, #firstName and #age"() { // … where: [firstName, lastName, age] << sql.rows("SELECT first_name, last_name, age FROM person;")}
Drop last parameter@Unrolldef "should set person data with #lastName, #firstName and #age"() { // … where: [firstName, lastName] << sql.rows("SELECT * FROM person;")}
Omit parameter@Unrolldef "should set person data with #lastName, #firstName and #age"() { // … where: [_, lastName, age] << sql.rows("SELECT * FROM person;")}
Exceptions
Not thrown exceptionPersonValidator sut = new PersonValidator()
def "should pass validation"() { given: Person person = new Person(firstName: "Tom", lastName: "Smith", age: 30) when: sut.validatePerson(person) then: notThrown(PersonValidationException)}
Not thrown exception - fails
Expected no exception of type 'com.blogspot.przybyszd.spock.bean.PersonValidationException' to be thrown, but got it nevertheless
Thrown exception@Unrolldef "should not pass validation"() { then: PersonValidationException exception = thrown(PersonValidationException) exception.message == message where: firstName | lastName | age | message "Tom" | "Smith" | -1 | "Age cannot be negative" "" | "Kowalski" | 19 | "First name must be given" "Jan" | null | 19 | "Last name must be given"}
Thrown exception - another exception
Expected exception of type 'com.blogspot.przybyszd.spock.bean.PersonValidationException', but got 'java.lang.RuntimeException'
Thrown exception - but no exception
Expected exception of type 'com.blogspot.przybyszd.spock.bean.PersonValidationException', but no exception was thrown
Mocking and stubbing
Creating mock
JdbcTemplate jdbcTemplate = Mock(JdbcTemplate)
PersonDao sut = new PersonDao(jdbcTemplate)
Validate mock calls
def "should persist one person"() { given: Person person = new Person("John", "Smith", 20) when: sut.persist(person) then: 1 * jdbcTemplate.execute("Insert into person (first_name, last_name, age) values ('John', 'Smith', 20)")}
Mock not called
Too few invocations for:
1 * jdbcTemplate.execute("Insert into person (first_name, last_name, age) values ('John', 'Smith', 20)") (0 invocations)
Unmatched invocations (ordered by similarity):
None
Too many calls
1 * jdbcTemplate.execute("Insert into person (first_name, last_name, age) values ('John', 'Smith', 20)") (2 invocations)
Matching invocations (ordered by last occurrence):
2 * jdbcTemplate.execute('Insert into person (first_name, last_name, age) values (\'John\', \'Smith\', 20)') <-- this triggered the error
Another parameters in callsdef "should persist many persons"() { given: List<Person> persons = [new Person("John", "Smith", 20), new Person("Jan", "Kowalski", 15)] when: sut.persist(persons) then: 1 * jdbcTemplate.execute("Insert into person (first_name, last_name, age) values ('John', 'Smith', 20)") 1 * jdbcTemplate.execute("Insert into person (first_name, last_name, age) values ('Jan', 'Kowalski', 15)")}
Any parameter
then: 2 * jdbcTemplate.execute(_)
Range of calls
then: (1..3) * jdbcTemplate.execute(_)
At least one call
then: (1.._) * jdbcTemplate.execute(_)
Any amount of calls
then: _ * jdbcTemplate.execute(_)
Two calls of method of any mock
then: 2 * _.execute(_)
Two calls of any method of mock
then: 2 * jdbcTemplate._(_)
Two calls of method by regex
then: 2 * jdbcTemplate./exe.*/(_)
Closure validates call
then: 2 * jdbcTemplate.execute({ String sql -> sql.endsWith("('John', 'Smith', 20)") || sql.endsWith("('Jan', 'Kowalski', 15)") })
Sequential callsdef "should persist many persons in order"() { given: List<Person> persons = [new Person("John", "Smith", 20), new Person("Jan", "Kowalski", 15)] when: sut.persist(persons) then: 1 * jdbcTemplate.execute("Insert into person (first_name, last_name, age) values ('John', 'Smith', 20)") then: 1 * jdbcTemplate.execute("Insert into person (first_name, last_name, age) values ('Jan', 'Kowalski', 15)")}
Define mock interactions in given block
given: jdbcTemplate = Mock(JdbcTemplate) { 2 * execute({ String sql -> sql.endsWith("('John', 'Smith', 20)") || sql.endsWith("('Jan', 'Kowalski', 15)") }) }
Stub
def "should find one person"() { given: jdbcTemplate.queryForList("select first_name, last_name, age from person where last_name = ?", ["Kowalski"]) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]] expect: sut.findByLastName("Kowalski") == [new Person("Jan", "Kowalski", 20)]}
Stub in context
given: jdbcTemplate = Stub(JdbcTemplate) { queryForList("select first_name, last_name, age from person where last_name = ?", ["Kowalski"]) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]] }
Any stub parameters
def "should find many times person"() { given: jdbcTemplate.queryForList(_, _) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]] expect: sut.findByLastName("Kowalski") == [new Person("Jan", "Kowalski", 20)] sut.findByLastName("Kowalski") == [new Person("Jan", "Kowalski", 20)]}
Multiple return values
def "should find many times person 2"() { given: jdbcTemplate.queryForList(_, _) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]] >> [[first_name: "Jan", last_name: "Kowalski", age: 25]] expect: sut.findByLastName("Kowalski") == [new Person("Jan", "Kowalski", 20)] sut.findByLastName("Kowalski") == [new Person("Jan", "Kowalski", 25)]}
Multiple return values as list
def "should find many times person 3"() { given: jdbcTemplate.queryForList(_, _) >>> [ [[first_name: "Jan", last_name: "Kowalski", age: 20]], [[first_name: "Jan", last_name: "Kowalski", age: 15]]] expect: sut.findByLastName("Kowalski") == [new Person("Jan", "Kowalski", 20)] sut.findByLastName("Kowalski") == [new Person("Jan", "Kowalski", 15)]}
Side effectsdef "should throw exception on second find"() { given: jdbcTemplate.queryForList(_, _) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]] >> { throw new DataRetrievalFailureException("Cannot retrieve data") } expect: sut.findByLastName("Kowalski") == [new Person("Jan", "Kowalski", 20)] when: sut.findByLastName("Kowalski") then: thrown(DataAccessException)}
Mocking and stubbing
def "should find one person and check invocation"() { when: List result = sut.findByLastName("Kowalski") then: result == [new Person("Jan", "Kowalski", 20)] 1 * jdbcTemplate.queryForList(_, _) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]]}
Any parameter list
then: 1 * jdbcTemplate.queryForList(*_) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]]
Validate parameter value
then: 1 * jdbcTemplate.queryForList(_, !(["Smith"] as Object[])) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]]
Validate parameter is not null
then: 1 * jdbcTemplate.queryForList(!null, _) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]]
Interaction blockdef "should find one person and check invocation external with first parameter not null"() { when: List result = sut.findByLastName("Kowalski") then: result == [new Person("Jan", "Kowalski", 20)] interaction { queryForListCalledOnceWithFirstName() }}
void queryForListCalledOnceWithFirstName(){ 1 * jdbcTemplate.queryForList(!null, _) >> [[first_name: "Jan", last_name: "Kowalski", age: 20]]}
SpiesList sut = Spy(ArrayList, constructorArgs: [10])
def "should use spy on list"() { given: sut.add(1) >> { callRealMethod() } sut.size() >> 10 when: sut.add(1) then: sut.size() == 10 sut.get(0) == 1}
Spring
Spring from configuration class@ContextConfiguration(classes = PersonContextConfiguration)class PersonContextFromClassTest extends Specification { @Autowired PersonController personController
@Autowired PersonDao personDao
//…}
Spring from configuration xml@ContextConfiguration(locations = "classpath:personContext.xml")class PersonContextFromXmlTest extends Specification {
@Autowired PersonController personController
@Autowired PersonDao personDao
//…}
Helper methods
Without helper
def "should check person"() { when: Person result = new Person("Tom", "Smith", 20) then: result != null result.firstName == "Tom" result.lastName == "Smith" result.age == 20}
Boolean helperdef "should check person with boolean helper method"() { when: Person result = new Person("Tom", "Smith", 20) then: checkPerson(result, "Tom", "Smith", 20)}boolean checkPerson(Person person, String firstName, String lastName, int age) { person != null && person.firstName == firstName && person.lastName == lastName && person.age == age}
Boolean helper - outputcheckPerson(result, "Tom", "Smith", 20)| |false com.blogspot.przybyszd.spock.dto.Person(Tom, Smith, 20)
Helper with assertdef "should check person with assert helper method"() { when: Person result = new Person("Tom", "Smith", 20) then: checkPersonWithAssert(result, "Tom", "Smith", 20)}void checkPersonWithAssert(Person person, String firstName, String lastName, int age) { assert person != null assert person.firstName == firstName assert person.lastName == lastName assert person.age == age}
Helper with assert - outputperson.firstName == "John"| | || Tom false| 3 differences (25% similarity)| (T)o(m-)| (J)o(hn)com.blogspot.przybyszd.spock.dto.Person(Tom, Smith, 20)
Withdef "should set first name, last name and age 1"() { when: Person person = new Person(firstName: "Bob", lastName: "Smith", age: 40) then: with(person) { firstName == "Bob" lastName == "Smith" age == 40 }}
Annotations
Ignoreclass IgnoreTest extends Specification { def "test 1"() { expect: 1 == 1 } @Ignore def "test 2"() { expect: 1 == 1 } def "test 3"() { expect: 1 == 1 }}
IgnoreRestclass IgnoreRestTest extends Specification { def "test 1"() { expect: 1 == 1 } @IgnoreRest def "test 2"() { expect: 1 == 1 } def "test 3"() { expect: 1 == 1 }}
IgnoreIfclass IgnoreIfTest extends Specification {
// @IgnoreIf({os.windows})// @IgnoreIf({os.linux}) @IgnoreIf({ System.getProperty("os.name").contains("Linux") }) def "test 1"() { expect: 1 == 1 }}
Requiresclass RequiresTest extends Specification {
// @Requires({os.windows})// @Requires({os.linux}) @Requires({ System.getProperty("os.name").contains("windows") }) def "test 1"() { expect: 1 == 1 }}
AutoCleanupclass AutoCleanupTest extends Specification {
JdbcTemplate jdbcTemplate = Mock(JdbcTemplate)
@AutoCleanup(value = "close", quiet = true) PersonDao sut = new PersonDao(jdbcTemplate)
def "test 1"() { expect: sut != null }}
FailsWithclass FailsWithTest extends Specification {
@FailsWith(RuntimeException) def "test 1"() { expect: throw new RuntimeException() }}
Timeout
class TimeoutTest extends Specification {
@Timeout(value = 750, unit = TimeUnit.MILLISECONDS) def "test 1"() { expect: 1 == 1 }}
Title
@Title("Title annotation is tested in this specification")class TitleTest extends Specification {
def "test 1"() { expect: 1 == 1 }}
Narrative
@Narrative("""Multiline narrative annotationis tested in this specification""")class NarrativeTest extends Specification {
def "test 1"() { expect: 1 == 1 }}
Subject
@Subject(Person)class SubjectTest extends Specification {
@Subject Person person = new Person("John", "Smith", 21)
def "should be adult"() { expect: person.isAdult() }}
Issue
class IssueTest extends Specification { @Issue(["http://example.org/mantis/view.php?
id=12345", "http://example.org/mantis/view.php?id=23"]) def "test 1"() { expect: 1 == 1 }}
Extra
void instead of def
void "should set first name from constructor"() { when: Person person = new Person(firstName: "Bob") then: person.firstName == "Bob"}
Shoud instead of def
Should "set first name from constructor"() { when: Person person = new Person(firstName: "Bob") then: person.firstName == "Bob"}
Shoud instead of def - how it works
import java.lang.Void as Should
Q & A
Thank you