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

grailsgrails-ormmockingspockstub

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

Best Answer

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.

Related Topic