Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Spock to stub both Gorm and other methods in a Grails domain class

Sorry if this is a newbie question but I would really appreciate any insights the community could offer with regard to a problem I am having with stubbing the following method which I have in a Grails service, LocationService.

Location locate(String target, String locator, Application app, boolean sync = true) {
    if (!target) throw new IllegalArgumentException("Illegal value for msid: " + target)
    def locRequest = Request.create(target, Type.LOCATE) 
    if (!locRequest.save()) {
            return Location.error(target, "Error persisting location request")
    }
    locationSource.locateTarget(target, locator, app, sync)
}

I have a domain class, Request, that as well as the default GORM methods also has some extra domain methods, eg. the create() method below

@EqualsAndHashCode
class Request {

    String reference
    String msid
    Type type
    Status status
    Destination destination
    DateTime dateCreated
    DateTime dateCompleted

    static create(String msid, Type type, Destination destination = Destination.DEFAULT) {
            new Request(reference: reference(type), type: type, status: Status.INITIATED, dateCreated: new DateTime())
    }

Finally, I have a Spock specification. I need to mock both the default GORM methods but also some stub some extra domain logic, eg, a static create method, in order to return a valid object to be persisted in the code under test.

Ideally, I would use Spock mocks but I can't use them here as according to the post below from Peter N, they need to be injected into the caller and in this case the Request (which I am trying to mock), is created as a local variable in the locate method in LocationService:

https://groups.google.com/forum/?fromgroups=#!topic/spockframework/JemiKvUiBdo

Nor can I use the Grails 2.x @Mock annotation as, although this will mock the GORM methods, I am unsure if i can mock/stub the additional static create() method from the Request class.

Hence, finally, I have been trying to use the Groovy StubFor / MockFor methods to do this as I believe that these will be used in the call to the test method by wrapping it in a use closure (as below).

Here is the test spec:

@TestFor(LocationService)
// @Mock(Request)
class LocationServiceSpec extends Specification {

    @Shared app = "TEST_APP"
    @Shared target = "123"
    @Shared locator = "999"

    def locationService = new LocationService()
    LocationSource locationSource = Mock()


  def "locating a valid target should default to locating a target synchronously"() {
      given:
            def stub = new StubFor(Request)
            stub.demand.create { target, type -> new Request(msid: target, type: type) }
            stub.demand.save { true }
            1 * locationSource.locateTarget(target, locator, app, SYNC) >> { Location.create(target, point, cellId, lac) }
            def location
      when: 
            stub.use {
                location = locationService.locate(target, locator, app)
            }
      then: 
            location
 }

However, when I run the test, although the stubbed create method returns my Request stub object, I get a failure on the stubbed save method:

groovy.lang.MissingMethodException: No signature of method:       com.domain.Request.save() is applicable for argument types: () values: []
Possible solutions: save(), save(boolean), save(java.util.Map), wait(), last(), any()

Could anybody please point out what I am doing wrong here or suggest the best approach to solve my particular case if needing to stub additional methods as well as GORM methods of a domain class that I can't inject directly into the code under test?

Thank you in advance,

Patrick

like image 326
Paddy Avatar asked Mar 21 '13 10:03

Paddy


1 Answers

I believe you should be able to use Grails' @Mock annotation like you mentioned for the GORM methods, and then you will need to manually mock the static methods:

@TestFor(LocationService)
@Mock(Request)// This will mock the GORM methods, as you suggested
class LocationServiceSpec extends Specification {
...
    void setup() {
        Request.metaClass.static.create = { String msid, Type type, Destination destination = Destination.DEFAULT ->
             //Some logic here
        }
    }
...

When using the @Mock annotation, Grails will mock the default methods (save/get/dynamic finders), but it doesn't do anything to any additional methods you may have added, so you need to manually mock those.

like image 188
Igor Avatar answered Nov 11 '22 09:11

Igor