Unit Testing a fluent API with mocking in Spock - unit-testing

Spock makes a strong distinction between a Stub and Mock. Use a stub when what to change want comes back from a class your class under test uses so that you can test another branch of an if statement. Use a mock, when you don't care what comes back your class under test just call another method of another class and you want to ensure you called that. It's very neat. However suppose you have a builder with a fluent API that makes people. You want to test a method that calls this Builder.
Person myMethod(int age) {
...
// do stuff
...
Person tony =
builder.withAge(age).withHair("brown").withName("tony").build();
return tony;
}
So originally, I was thinking just mock the builder and then the unit test for myMethod() should check withAge(), withHair() with the right parameters.
All cool.
However -- the mock methods return null. Meaning you can't use the fluent API.
You could do.
Person myMethod(int age) {
...
// do stuff
...
builder.withAge(age);
builder.withHair("brown");
builder.withName("tony");
builder.build();
return tony;
}
which works. You test will work but it defeats the purpose of using the fluent API.
So, if you are using fluent APIs, do you stub or mock or what?

Management summary
If you do not need to verify interactions like 1 * myMock.doSomething("foo"), you can use a Stub instead of a Mock, because while mocks always return null, false or 0, stubs return a more sophisticated default response, e.g. empty objects rather than null and - most importantly - the stub itself for methods with a return type matching the stubbed type. I.e., testing fluent APIs with stubs is easy.
If however you wish to also verify interactions, you cannot use a Stub and have to use a Mock instead. But there the default response is null, i.e. you need to override it for the fluent API methods. This is quite easy in both In Spock 1.x and 2.x. Specifically in 2.x, there is some syntactic sugar for it, making the code even smaller.
Classes under test
Quick & dirty implementation, just for illustration:
package de.scrum_master.stackoverflow.q57298557
import groovy.transform.ToString
#ToString(includePackage = false)
class Person {
String name
int age
String hair
}
package de.scrum_master.stackoverflow.q57298557
class PersonBuilder {
Person person = new Person()
PersonBuilder withAge(int age) {
person.age = age
this
}
PersonBuilder withName(String name) {
person.name = name
this
}
PersonBuilder withHair(String hair) {
person.hair = hair
this
}
Person build() {
person
}
}
Test code
Testing the original class, no mocking
package de.scrum_master.stackoverflow.q57298557
import spock.lang.Specification
class PersonBuilderTest extends Specification {
def "create person with real builder"() {
given:
def personBuilder = new PersonBuilder()
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
person.age == 22
person.hair == "blonde"
person.name == "Alice"
}
}
Simple stubbing without interaction testing
This is the simple case and works for both Spock 1.x and 2.x. Add this feature method to your Spock specification:
def "create person with stub builder, no interactions"() {
given:
PersonBuilder personBuilder = Stub()
personBuilder.build() >> new Person(name: "John Doe", age: 99, hair: "black")
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
person.age == 99
person.hair == "black"
person.name == "John Doe"
}
Mock with custom default response
Just tell Spock to use stub-like default responses for your mock:
import org.spockframework.mock.EmptyOrDummyResponse
// ...
def "create person with mock builder, use interactions"() {
given:
PersonBuilder personBuilder = Mock(defaultResponse: EmptyOrDummyResponse.INSTANCE)
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
3 * personBuilder./with.*/(_)
1 * personBuilder.build() >> new Person(name: "John Doe", age: 99, hair: "black")
person.age == 99
person.hair == "black"
person.name == "John Doe"
}
The syntax above works for bork Spock 1.x and 2.x. Since version 2.0-M3, Spock users can tell their mocks/spies to return a stub-like default response using the syntactic sugar syntax >> _, e.g. in the simplest case
Mock() {
_ >> _
}
Thanks to Spock maintainer Leonard Brünings for sharing this neat little trick.
Then later in the then: or expect: block, you can still define additional interactions and stub responses, overriding the default. In your case, it could look like this:
import spock.lang.Requires
import org.spockframework.util.SpockReleaseInfo
//...
#Requires({ SpockReleaseInfo.version.major >= 2})
def "create person with mock builder, use interactions, Spock 2.x"() {
given:
PersonBuilder personBuilder = Mock()
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
3 * personBuilder./with.*/(_) >> _
1 * personBuilder.build() >> new Person(name: "John Doe", age: 99, hair: "black")
person.age == 99
person.hair == "black"
person.name == "John Doe"
}
Original answer
Before I realised that Spock's own EmptyOrDummyResponse, which is used by stubs by default, actually returns the mock instance for methods matching with a return type matching the mocked/stubbed class, I thought it would just return an empty object like for methods with other return types, i.e. empty strings, collections etc. Therefore, I invented my own ThisResponse type. Even though it is not necessary here, I am keeping the old solution, because it teaches users how to implement and use custom default responses.
If you want a generic solution for builder classes, you can use à la carte mocks as described in the Spock manual. A little caveat: The manual specifies a custom IDefaultResponse type parameter when creating the mock, but you need to specify an instance of that type instead.
Here we have our custom IDefaultResponse which makes the default response for mock calls not null, zero or an empty object, but the mock instance itself. This is ideal for mocking builder classes with fluent interfaces. You just need to make sure to stub the build() method to actually return the object to be built, not the mock. For example, PersonBuilder.build() should not return the default PersonBuilder mock but a Person.
package de.scrum_master.stackoverflow.q57298557
import org.spockframework.mock.IDefaultResponse
import org.spockframework.mock.IMockInvocation
class ThisResponse implements IDefaultResponse {
public static final ThisResponse INSTANCE = new ThisResponse()
private ThisResponse() {}
#Override
Object respond(IMockInvocation invocation) {
invocation.mockObject.instance
}
}
Now you can use ThisResponse in your mocks as follows:
def "create person with a la carte mock builder, use interactions"() {
given:
PersonBuilder personBuilder = Mock(defaultResponse: ThisResponse.INSTANCE) {
3 * /with.*/(_)
1 * build() >> new Person(name: "John Doe", age: 99, hair: "black")
}
when:
def person = personBuilder
.withHair("blonde")
.withAge(22)
.withName("Alice")
.build()
then:
person.age == 99
person.hair == "black"
person.name == "John Doe"
}

Related

Failing to save during Test on Controller

I have been struggling with this for quite sometime and it seems like a common problem, with no solution that worked for me.
What I am trying to do is test the controller which calls a save(), and inside the save() the method calls a service to save the employee with the given command objects.
This is how the code looks like:
def save(EmployeeInputCommand employeeCommand, AddressCommand addressCommand) {
if (addressCommand.address.validate()) {
println "Validated address input"
employeeManagerService.save(employeeCommand, addressCommand)
println "Employee should be saved"
println "------------------------->"
println "${employeeGetterService.each {errors}}"
flash.message = "Employee has been successfully created!"
redirect(action: "index")
} else {
println "Addrescommand didnt validate: ${addressCommand.errors}"
flash.message = "Please input valid data!!!"
redirect(action: "create", model: [employee: employeeCommand, address: addressCommand])
}
}
The service contains this:
def save(EmployeeInputCommand employeeCommand, AddressCommand addressCommand) {
def employee = new Employee(employeeCommand.properties)
employee.address = addressCommand.address
for (id in employeeCommand.s) {
employee.addToSkills(Skill.get(id))
}
if (employee.validate()) {
employee.address.save()
employee.save()
}
else {
return false
}
}
I know this works when I try to actually save the employee in my application, but during the Unit Test process nothing happens.
My Test:
def "Create an Employee with good params"() {
given: "A valid employee command object"
def employeeCommand = new EmployeeInputCommand(
name: "John",
surname: "Smith",
birthdate: Date.parse("yyyy-MM-dd", "1992-06-08"),
salary: 21000,
s: [1, 3, 5]
)
and: "Also a valid address command object"
def addressCommand = new AddressCommand(
street: "Green Alley",
houseid: "42",
city: "Odense",
county: "Fyn",
postCode: "5000 C"
)
expect:
addressCommand.validate()
!Employee.findByNameAndSurname("John", "Smith")
when: "Saving employee"
request.method = "POST"
controller.save(employeeCommand, addressCommand)
Employee employee = Employee.findByNameAndSurname("John", "Smith")
println Employee.list()
then: "The employee is created, and browser redirected"
Employee.count == 4
employee
employee.skills.size() == 3
employee.address.postCode == "5000 C"
}
The test is failing with a null error when the controller.save() is called. I have spend too much time trying to solve this, which has all been in vain
This is the output screenshot
I don't believe testing a controller should cover the service logic. I'd mock the service, if I were to write a unit test for a controller, and test the service separately in its own unit/integration tests.
For example:
/*
* in controller spec
*/
def setup() {
def serviceMock = new MockFor(EmployeeManagerService)
serviceMock.ignore('save') { ec, ac ->
Employee.buildWithoutSave().save(validate: false)
}
serviceMock.use {
controller.employeeManagerService = new EmployeeManagerService()
}
}
def 'testFoo'() {
when:
// ...
controller.employeeManagerService.save()
// ...
then:
// ...
}
Note that the code is using the excellent Build Test Data plugin.
I think the issue is with service in unit testing. In unit testing, you need defineBeans closure to use spring beans-
void "test the filter"() {
setup:
defineBeans {
employeeManagerService(EmployeeManagerService) { bean ->
bean.autowire = true
}
}
when:
...
then:
...
}
Ref# Inject Services in Grails Unit Test
You need to perform your tests as Integration Tests, not Unit Tests. Integration testing is needed when you're testing database-related logic (CRUD ops), not just mocking the CRUD ops which is what unit tests do.

how to avoid a domain call in unit testing with grails / spock

I have the following lines of code
username = username.stripIndent()
user = User."${databaseInstance}".findByUsername(username)
if (user == null){
return "User does not exist"
}
I'm trying to unit test this functionality with:
def setup() {
def mockUser = Mock(User)
myClass.user = mockUser
}
void "userNotFoundGetUserInfo"(){
given:
myClass.databaseInstance = 'X'
_ * mockUser._ >> null
when:
def result = myClass.getUserInfo(username)
then:
result == "User does not exist"
}
but I keep getting an error of "No such property: X for class mypackage.User"
I realize this is because i'm mocking the "user" object and not the "User" class, so how do I get around the fact that my code is making a direct call to a domain class?
Use Grails built-in #Mock annotation instead of Spock's Mock() method. Go like:
#Mock(User)
class YourTestSpecification extends Specification {
def setup() {
myClass.user = new User()
}
}
#Mock is meant to mock Grails domain classes.

How to mock a HttpServletRequest's BufferedReader payload in Grails/Spock

I have a Grails controller that expects an XML payload.
I fetch the XML payload like this in a Grails controller.
def xmlPayload = request.reader.text
That part works fine, but I'm struggling to mock this payload in a unit test.
I've tried both of the following, but the debugger is showing 'request.reader' to be null in both approaches.
Approach #1:
void "test controller method"(){
setup:
def mockBufferedReader = Mock( BufferedReader )
mockBufferedReader.getText() >> '<hello/>'
request.getReader() >> mockBufferedReader
....
Approach #2:
void "test controller method"(){
setup:
def mockBufferedReader = Mock( BufferedReader )
mockBufferedReader.getText() >> '<hello/>'
request.metaClass.getReader = { -> mockBufferedReader }
....
'request' in a unit test is a GrailsMockHttpServletRequest, so I presumed I could mock its methods like this (3rd line of both approaches), but so far no luck.
Thanks for any ideas.
You can do:
class EchoController {
def echo () {
render (request.reader.text)
}
}
#TestFor(EchoController)
class EchoSpec extends Specification {
def "echos XML data" () {
request.XML = '<hello/>'
when:
controller.echo ()
then:
response.text == '<hello/>'
}
}
See Testing XML and JSON Requests in Unit Testing Controllers.
If you only need to provide contents for a request, then you don't need to mock anything.
def "Spock works as expected"() {
given:
def request = new GrailsMockHttpServletRequest(content: '<hello/>')
when:
def result = request.getReader().getText()
then:
result == '<hello/>'
}
One purpose of such Mock classes (as in Spring Test etc.) is to avoid explicit mocking with external libraries.

Unit testing controllers in Play framework with SecureSocial

I am trying to invent some kind of mocking SecureSocial action generators, or SecureSocial itself to be able to unit-test controller methods.
I've found some approaches, like Unit-testing methods secured with Securesocial annotation and Testing a Play2 application with SecureSocial using dependency injection but the thing is, that in that questions authors, in fact, don't do unit testing, but integration testing.
My unit tests look like this:
trait MockDaoProvider extends IDaoProvider {
def entityDao = entityDaoMock
}
val controller = new MyController with MockDaoProvider
"MyController.list" should {
"return an OK" in {
entityDaoMock.list().returns(List())
val result = controller.list()(FakeRequest())
status(result) must equalTo(OK)
}
}
As one can see, I mocked dependencies to isolate and test the behavior that controller method actually does.
Everything was OK until I used SecuredAction from securesocial for MyController.list method. Now I get an exception, and the test fails. I have no idea how I could mock, stub or override SecuredAction and UserAwareAction objects from securesocial. Still I don't want to convert my tests into route(...) tests. They are intended to test only the controller's behavior.
Have someone encountered the same problem? May be there are any hints how it could be solved?
PS: Play framework 2.2.1, securesocial - 2.1.2
It seem like the author of the code really hasn't emphasized testability, which has forced users to come up with their own novel solutions. This one by user jeantil could be helpful:
class FakeAuthenticatorStore(app:Application) extends AuthenticatorStore(app) {
var authenticator:Option[Authenticator] = None
def save(authenticator: Authenticator): Either[Error, Unit] = {
this.authenticator=Some(authenticator)
Right()
}
def find(id: String): Either[Error, Option[Authenticator]] = {
Some(authenticator.filter(_.id == id)).toRight(new Error("no such authenticator"))
}
def delete(id: String): Either[Error, Unit] = {
this.authenticator=None
Right()
}
}
abstract class WithLoggedUser(val user:User,override val app: FakeApplication = FakeApplication()) extends WithApplication(app) with Mockito{
lazy val mockUserService=mock[UserService]
val identity=IdentityUser(Defaults.googleId, user)
import helpers._
import TestUsers._
def cookie=Authenticator.create(identity) match {
case Right(authenticator) => authenticator.toCookie
}
override def around[T: AsResult](t: =>T): execute.Result = super.around {
mockUserService.find(Defaults.googleId) returns Some(identity)
UserService.setService(mockUserService)
t
}
}
val excludedPlugins=List(
,"service.login.MongoUserService"
,"securesocial.core.DefaultAuthenticatorStore"
)
val includedPlugins = List(
"helpers.FakeAuthenticatorStore"
)
def minimalApp = FakeApplication(withGlobal =minimalGlobal, withoutPlugins=excludedPlugins,additionalPlugins = includedPlugins)
which then allows testing like this
"create a new user password " in new WithLoggedUser(socialUser,minimalApp) {
val controller = new TestController
val req: Request[AnyContent] = FakeRequest().
withHeaders((HeaderNames.CONTENT_TYPE, "application/x-www-form-urlencoded")).
withCookies(cookie) // Fake cookie from the WithloggedUser trait
val requestBody = Enumerator("password=foobarkix".getBytes) andThen Enumerator.eof
val result = requestBody |>>> controller.create.apply(req)
val actual: Int= status(result)
actual must be equalTo 201
}
After some thinking, probing and experimenting I've ended up with an elegant solution. The solution relies on "cake pattern" of dependency injection. Like this:
Code in controller:
trait AbstractSecurity {
def Secured(action: SecuredRequest[AnyContent] => Result): Action[AnyContent]
}
trait SecureSocialSecurity extends AbstractSecurity with securesocial.core.SecureSocial {
def Secured(action: SecuredRequest[AnyContent] => Result): Action[AnyContent] = SecuredAction { action }
}
abstract class MyController extends Controller with AbstractSecurity {
def entityDao: IEntityDao
def list = Secured { request =>
Ok(
JsArray(entityDao.list())
)
}
}
object MyController extends MyController with PsqlDaoProvider with SecureSocialSecurity
And test code:
trait MockedSecurity extends AbstractSecurity {
val user = Account(NotAssigned, IdentityId("test", "userpass"), "Test", "User",
"Test user", Some("test#user.com"), AuthenticationMethod("userPassword"))
def Secured(action: SecuredRequest[AnyContent] => play.api.mvc.Result): Action[AnyContent] = Action { request =>
action(new SecuredRequest(user, request))
}
}
val controller = new MyController with MockDaoProvider with MockedSecurity
"IssueController.list" should {
"return an OK" in {
entityDaoMock.list().returns(List())
val result = controller.list()(FakeRequest())
status(result) must equalTo(OK)
}
}
Still there is a drawback - the tests depends on securesocial classes as well... but... is it really a drawback?
I don't know how this approach will work in more complex situations, we'll see.

Unit testing custom validator of command object with dependency

I have a command object for registering user, and I want to check how old is the user. This command object has a service dependency. How can I test custom validator for my dateOfBirth property? As it looks now is taken straight from documentation, here.
class RegisterUserCommand {
def someService
String username
String password
String password2
String email
Date dateOfBirth
static constraints = {
// other constraints
dateOfBirth blank: false, validator: {val, obj ->
return obj.someService.calculateAge(val) >= 18
}
}
So basically the question is: how can I mock 'obj' parameter of the validator closure?
The easiest way to test validation on a command object is to use GrailsUnitTestCase.mockForConstraintsTests. A mock validate method will be applied to your command object, and you can just call validate() like you would outside of a test.
Here's an example of how you could write your unit test. The blank constraint isn't meaningful for dates, so I've changed it to nullable: false.
import grails.test.GrailsUnitTestCase
class RegisterUserCommandTests extends GrailsUnitTestCase {
RegisterUserCommand cmd
protected void setUp() {
super.setUp()
cmd = new RegisterUserCommand()
mockForConstraintsTests RegisterUserCommand, [cmd]
}
void testConstraintsNull() {
cmd.dateOfBirth = null
cmd.someService = [calculateAge: { dob -> 18 }]
def result = cmd.validate()
assert result == false
assert cmd.errors.getFieldErrors('dateOfBirth').code == ['nullable']
}
void testConstraintsCustom() {
cmd.dateOfBirth = new Date()
cmd.someService = [calculateAge: { dob -> 17 }]
def result = cmd.validate()
assert result == false
assert cmd.errors.getFieldErrors('dateOfBirth').code == ['validator.invalid']
}
}
Note that your service won't get injected in a unit test (it will in an integration test though), so you'll either need to mock it, as above, or create an instance and assign it to cmd.someservice.