Spock is a nice groovy based testing framework and all though it can be very easy to get you started with spock, this blog will show some non-standard things that you can do as well with spock. For more information about the spock framework (and you should definitely check this out if you are not familiar yet with this great framework), see the Spock framework link
Changing parameters for a void method.
This one is very easy using spock, for instance, giving the following flow in a service method:
– get some object from the database for a given identifier
– apply changes from the web object
– validate the merged object
– and if no errors found, store the object back in the database
The validation can cancel the storing of the object, however if you are using the spring validation, then there is no return value to indicate whether the validation was successful or not. If there are errors, the model validation is supposed to add these to the result, and it is up to the service layer to react on this, in pseudo code:
public void store(Long id, SomeForm form, Errors errors) { SomeObject persistedObject = dao.get(id); persistedObject.merge(form); validator.validate(persistedObject, errors); if (!errors.hasErrors()) { dao.saveOrUpdate(persistedObject); } }
So when creating a test for spock, you would like the validate method to add an error to the errors. Well that is easy with spock:
given: validator.validate(_, errors) >> errors.reject("some error")
What happens here is that in the setup phase we are telling spock that whenever there is a call to validator.validate with an unspecified object and an error object, please add one error to the errors. The complete test could become then something like this:
def "should not store my object if the validator disagrees"() { given: dao.get(1) >> new SomeObject(id:1, name:"some name") validator.validate(_, errors) >> errors.reject("some error") when: service.store(1, someForm, errors) then: 0 * dao.saveOrUpdate(_) }
Another nice thing of spock and groovy is that the fields can be assigned without using setters. That is what I am doing in this line
dao.get(1) >> new SomeObject(id:1, name:"some name")
Which will tell spock, if a dao.get method is done with parameter 1, then spock should return a new SomeObject with the id set to 1 and the name is set to “some name”.
Checking an object that gets updated in a service method and is not returned
This can happen when you have a service method, that sets some fields based on certain conditions and then stores the object in the database. Pseudo code:
public void store(Long id, SomeForm form, Errors errors) { SomeObject persistedObject = dao.get(id); persistedObject.merge(form); validator.validate(persistedObject, errors); if (!errors.hasErrors()) { persistedObject.setStatus("waiting for review"); dao.saveOrUpdate(persistedObject); } }
This is more or less the same method, however if there are no errors a status field should be set. Since this is also not returned, this is kind of hard to test correctly in other testing frameworks. Spock however, makes it easy (again). This is how the test should look:
def "should store my object with an updated status"() { given: dao.get(1) >> new SomeObject(id:1, name:"some name") when: service.store(1, someForm, errors) then: 1 * dao.saveOrUpdate({it.status == "waiting for review"}) }
Now it says, that a dao.saveOrUpdate call should have been done with an unspecified object. The only thing that I am interested in, in this unspecified object, is that it should have a status field that says “waiting for review”
Checking an object that gets updated in a service method and returns something
If you do not want to test the dao, but rather a calling method and the dao should update something, spock can help as well:
def "should do some logic and store"() { given: dao.get(1) >> new SomeObject(id:1, name:"some name") when: SomeObject updatedObject = service.store(1, someForm, errors) then: 1 * dao.saveOrUpdate(_) >> {SomeObject someObject -> someObject.setStatus("waiting for review") return someObject } updatedObject.status == "waiting for review"
Spock will intervene when the dao.saveOrUpdate method is called and will run the code that is inside the closure. Note that in the previous example, the declaration could have been skipped and the keyword it could have been used, but if you have multiple parameters you have to define them using this syntax.
Testing boundaries.
To test boundaries, you normally have to create a number of test cases that will test just before the boundary, on the boundary and just after the boundary. If you do this in normal testing you might end up with a number of setup calls that each method needs or just putting all the testing of boundaries in one test method. I prefer all my boundaries tests to be defined in separate test methods, for better reading and understanding. Spock makes this easy again, with the expect and when keywords. For instance, when ordering a ticket to a conference, if the ticket is ordered 150 days in advance, you can get a 20 percent discount, if you order it 100 days in advance you can get a 15 percent discount, and after that you will not get a discount anymore. Using spock this could be done as follows:
def "should calculate the correct discounts based on days in advance"() setup: // do your setup here to get the service ready expect: discount = service.getDiscount(numberOfDaysBeforeStart) where: numberOfDaysBeforeStart | discount 151 | 20 150 | 20 149 | 15 101 | 15 100 | 15 99 | 0
In the where clause, the first line defines the parameters that spock will use internally in the expect clause. Then for each following line, a new test case will be run, with the given parameters. And voila, I just created 6 test cases to test my boundaries, ehr, to test the boundaries of this method call.
One thought on “Advanced spock testing.”
Comments are closed.