Czy można pisać testy bez takich bibliotek jak JUnit, TestNG i festAssert? Mockować bez Mockito? Łapać wyjątki bez catch-exception? Oczywiście, że można. Odpowiedzią jest Spock.
Podczas prezentacji przyjrzymy się możliwościom Spocka w testowaniu kodu javowego i grooviowego. Zobaczymy jak pisać w stylu given-when-then, sprawdząć warunki nie używając słowa "assert", jak zasilać testy danymi, udawać, że mamy zależności testowanego obiektu i pisać testy integracyjne z podniesionym kontekstem springa.
4. PersonValidator.java Part. 1
@Component
public 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");
}
5. 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");
}
}
}
16. 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"
}
}
18. 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"
}
19. Given block
def "should set first name from setter"() {
given:
Person person = new Person(firstName: "Bob")
when:
person.firstName = 'Tom'
then:
person.firstName == "Tom"
}
20. 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
}
21. 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"
}
22. 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"
}
23. Expect block
def "should compare person with equals"() {
expect:
new Person("Bob", "Smith", 15) == new Person
("Bob", "Smith", 15)
}
33. Parameters in table
@Unroll
def "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
}
34. Parameters in method signature
@Unroll
def "should set person data 2"(String firstName, String
lastName, int age) {
// …
where:
lastName | firstName | age
"Smith" | "John" | 25
"Kowalski" | "Jan" | 24
}
35. Parameters in method name
@Unroll
def "should set person with #lastName, #firstName and
#age"() {
// …
where:
lastName | firstName | age
"Smith" | "John" | 25
"Kowalski" | "Jan" | 24
}
36. Call parameterless method in test name
Part 1
@Unroll("should set person with #lastName.length(),
#firstName.toUpperCase() and #age")
37. 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)
}
38. Separeted table
@Unroll
def "should check if person is adult with table"() {
expect:
new Person(age: age).isAdult() == adult
where:
age || adult
17 || false
18 || true
19 || true
}
39. Parameters from list
@Unroll
def "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
}
40. Parameters from list of list
@Unroll
def "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]]
}
41. One paramter table
@Unroll
def "should set first name"() {
when:
Person person = new Person(firstName: firstName)
then:
person.firstName == firstName
where:
firstName | _
"John" | _
"Jan" | _
}
42. Parameters from db - setup
static 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);""")
}
44. All parameters from db
@Unroll
def "should set person data with #lastName, #firstName
and #age"() {
// …
where:
[firstName, lastName, age] << sql.rows("SELECT *
FROM person;")
}
45. All parameters from db by name
@Unroll
def "should set person data with #lastName, #firstName
and #age"() {
// …
where:
[firstName, lastName, age] << sql.rows("SELECT
first_name, last_name, age FROM person;")
}
46. Drop last parameter
@Unroll
def "should set person data with #lastName, #firstName
and #age"() {
// …
where:
[firstName, lastName] << sql.rows("SELECT * FROM
person;")
}
47. Omit parameter
@Unroll
def "should set person data with #lastName, #firstName
and #age"() {
// …
where:
[_, lastName, age] << sql.rows("SELECT * FROM
person;")
}
49. Not thrown exception
PersonValidator 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)
}
50. Not thrown exception - fails
Expected no exception of type 'com.blogspot.przybyszd.
spock.bean.PersonValidationException' to be thrown, but
got it nevertheless
51. Thrown exception
@Unroll
def "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"
}
52. Thrown exception - another exception
Expected exception of type 'com.blogspot.przybyszd.
spock.bean.PersonValidationException', but got 'java.
lang.RuntimeException'
53. Thrown exception - but no exception
Expected exception of type 'com.blogspot.przybyszd.
spock.bean.PersonValidationException', but no exception
was thrown
56. 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)")
}
57. 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
58. 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
59. Another parameters in calls
def "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)")
}
91. With
def "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
}
}
105. void instead of def
void "should set first name from constructor"() {
when:
Person person = new Person(firstName: "Bob")
then:
person.firstName == "Bob"
}
106. Shoud instead of def
Should "set first name from constructor"() {
when:
Person person = new Person(firstName: "Bob")
then:
person.firstName == "Bob"
}
107. Shoud instead of def - how it works
import java.lang.Void as Should