Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking static get gorm method in Grails unit test

I'm using Grails 2.3.7. I have a simple Person domain object:

class Person {
  String name
  static constraints = {}
}

And controller:

@Transactional(readOnly = true)
class PersonController {
  def index() {
    render view:'index', model:[personList:Person.list()]
  }

  def show(Long id) {
    def person = Person.get(id)
    render view:'show', model:[person:person]
  }
  ...
}

I'm trying to write a unit test for the controller to exercise the show method, but I am not sure how I need to configure the test to ensure that the static call Person.get(id) returns a value.

Here's my (failing) attempt:

import grails.test.mixin.*
import spock.lang.*

@TestFor(PersonController)
@Mock(Person)
class PersonControllerSpec extends Specification {

  void "Test show action loads Person by id and includes that person in the model rendered"() {
    setup:
      def personMockControl = mockFor(Person)
      personMockControl.demand.static.get() { int id -> new Person(name:"John") }

    when:"A valid person id passed to the show action"
      controller.show(123)

    then:"A model is populated containing the person domain instance loaded by that unique id"
      model.person != null
      model.person.name == "John"
  }
}

This test fails because the condition model.person != null fails. How do I rewrite this test in order to make it pass?

EDIT

In this case I can side-step the static method call by reworking the test thusly:

void "Test show action loads Person by id and includes that person in the model rendered"() {
  setup:
    def p = new Person(name:"John").save()

  when:"A valid person id passed to the show action"
    controller.show(p.id)

  then:"A model is populated containing the person domain instance loaded by that unique id"
    model.person != null
    model.person.id == p.id
    model.person.name == "John"
}

However, I'd really like to understand how to correctly mock the Person's static get method.

EDIT 2

Here's another situation to demonstrate my perceived need to mock the get method. Given this filter code:

def fitlers = {
  buyFilter(controller:"paypal", action:"buy") {
    after = {
      new VendorPayment(payment:request.payment, vendor: Vendor.get(params.buyerId)).save()
    }
  }
}

I'm trying to test that this filter works as expected using the following test:

void "test the buyFilter to ensure it creates a VendorPayment when the PayPal plugin controller is called" () {
  setup:
  // how do I mock the Vendor.get(params.buyerId)
  // to return a vendor in order to save a new VendorPayment

  when:
  withFilters(action:"buy") {
    controller.buy()
  }

  then:
  VendorPayment.count() == 1
}
like image 586
Brice Roncace Avatar asked Apr 28 '14 22:04

Brice Roncace


1 Answers

Like dmahaptro mentioned, you do not need to mock your person. The preferred/proper way of grabbing an object in a test via .get() is the way you have it--you create the domain instance, and pass its id to the controller action for consumption.

However, if you need to mock a static method like this there are two approaches:

1) Use the mockFor method provided by GrailsUnitTestMixin and then specify the static method like so:

GrailsMock mockPerson = mockFor(Person)
mockPerson.demand.static.get() { Long id -> new Person(name:"John") }

Note that in your case, the mock's static get method did not contain the correct parameter type (int was used instead of Long) so this method was not being invoked.

2) Modify the class' metaClass. Changing the metaClass is highly discouraged (especially when the @Mock annotation already sets everything up for you), and can lead to test leakage (i.e., other tests will not work properly after you've changed the metaClass, unless you restore the original metaclass after the test is done).

That said, for the sake of familiarity, the way you would mock the .get() method is via:

Person.metaClass.static.get = { Long id -> return personWithId }

Again, this is considered bad practice and should be avoided, but there you have it.

like image 122
Igor Avatar answered Oct 24 '22 01:10

Igor