How to test Grails service using Spock?
Asked Answered
Y

2

10

I have a EncouragementService.groovy with following method

  class EncouragementService {
   def stripePaymentService 

   def encourageUsers(List<User> users){
     if(null != users && users.size()>0){
      for(User user : users){
        //logic
        stripePaymentService.encourage(user)
        //
      }
     }   
   }
  }

To test above code in JAVA universe, using JUnit I would first create two or three users in setup. Pass the list of users to encourageUsers(...) method and check whatever I want with the result.

How can I achieve the same thing here in grails,

import com.github.jmkgreen.morphia.Datastore;

@TestFor(EncouragementService)
class EncouragementServiceSpec {

  def morphiaService = new MorphiaService()

  void testEncourageUsers() {
        List<User> users = createUsers();
        encouragementService.(users)  
            //
  }

  def createUsers(){
       Datastore datastore = morphiaService.dataStoreInstance()
       def user = new User()
       user.setName("Prayag Upd")
       //
       datastore.save(user)
       [user]
  }
}

I am using spock:0.7

plugins {
    test(":spock:0.7") { exclude "spock-grails-support" }
}
Yellowlegs answered 5/9, 2013 at 19:40 Comment(0)
U
13

Service class can be optimized as below:

class EncouragementService {
   def encourageUsers(List<User> users){
       if(users){ //Groovy truth takes care of all the checks
          for(user in users){
            //logic
          }
       }   
   }
}

Spock Unit Test:
Spock takes testing to whole another level, where you can test the behavior (adheres to BDD). The test class would look like:

import spock.lang.*

@TestFor(EncouragementService)
@Mock(User) //If you are accessing User domain object.
class EncouragementServiceSpec extends Specification{
  //def encouragementService //DO NOT NEED: mocked in @TestFor annotation

  void "test Encourage Users are properly handled"() {
      given: "List of Users"
          List<User> users = createUsers()

      when: "service is called"
          //"service" represents the grails service you are testing for
          service.encourageUsers(users) 

      then: "Expect something to happen"
          //Assertion goes here
  }

  private def createUsers(){
       return users //List<User>
  }
}
Uptotheminute answered 5/9, 2013 at 19:52 Comment(7)
"Patience" is my name, I am not joking ;). Frankly speaking, recently I had a presentation on spock in local Grails User group, so I thought I would brag about something I worked on. ;) @SérgioMichelsUptotheminute
@Uptotheminute When User is not a grails-app/domain based entity, I get Failure: test Users are properly handled(np.zazzercode.service.EncouragementServiceSpec) | org.codehaus.groovy.grails.exceptions.GrailsConfigurationException: Cannot add Domain class [class np.zazzercode.domain.User]. It is not a Domain! at grails.test.mixin.domain.DomainClassUnitTestMixin.registerGrailsDomainClass(DomainClassUnitTestMixin.groovy:209) Yellowlegs
@PrayagUpd Yea, if User is not a domain class, then you would not need @Mock that is what I meant in the answer.Uptotheminute
@Uptotheminute Ok got that, But how do I initialize a service(eg. stripePaymentService) used inside EncouragementService. Since that is not being injected, throws java.lang.NullPointerException: Cannot invoke method encourage() on null object while testing appYellowlegs
@Uptotheminute Well it's fixed adding def stripePaymentService = Mock(StripePaymentService) service.stripePaymentService = stripePaymentService to givenYellowlegs
Also you need to mock the encourage method inside the stripePaymentService, right?Estivation
Yes, correct. @TestFor takes care of the current service class under test. But other services have to be mocked.Uptotheminute
L
4

Use the build-test-data plugin to build the users.

@TestFor(EncouragementService)
@Build(User)
class EncouragementServiceSpec extends Specification {

  def "encourage users does x"() {
    given:
    def users = createUsers();

    when:
    service.encourageUsers(users)  

    then:
    // assert something
  }

  def createUsers(){
    [User.build(), User.build(), User.build()]
  }
}

I've also made a couple of changes to the code to make it a proper spock specification. Your test has to extend Specification and you might want to familiarize yourself with Spock's keywords.

Lacefield answered 5/9, 2013 at 19:49 Comment(1)
build-test-data plugin looks awesome, but doesn't seem to work for non grails-app/domain based entities. Throws exception org.codehaus.groovy.grails.exceptions.GrailsConfigurationException: Cannot add Domain class [class np.zazzercode.domain.User]. It is not a DomainYellowlegs

© 2022 - 2024 — McMap. All rights reserved.