Ectoras
Ectoras

Reputation: 1307

Grails Spock Mocking an Object

I am new in unit-testing in Grails application using Spock. However I would like to ask the following question. Lets say I want to run a test for the following function testfun.

class TestFun{

boolean testfun(long userId, long orderId){

    User user = User.findByUserId(userId)
    if(user == null)return false
    Order order = Order.findByUserAndId(user, orderId)

    HashMap<String, Object> = orderContent   
    orderContent= order.orderContent // the order has an element orderContent for storing the elements that one user orders

    if(orderContent!=null){
      orderContent.put("meal",1)
      order.orderContent = orderContent
      return true
    }

    return false
}
}

The corresponding unit test in that case would be:

class TestFun extends Specification {

    def setup() {

        GroovySpy(User, global: true)
        GroovySpy(Order, global: true)
    }
 def "test funtest"() {

        User user = new User(2).save()
        Order order = new Order(3).save()

        when:
        service.testfun(2,3) == result

        then:
        2*User.findByUserId(2) >> Mock(User)
        1*Order.findByUserAndId(_ as User, 1)>> Mock(Order)
        result == true
}
}

However, I think that I have to mock the order.orderContent and I do not know how to mock it. Right now the test fails, because the orderContent is null so the testfun returns false.

Can anyone help me on that?

Upvotes: 1

Views: 3261

Answers (1)

mnd
mnd

Reputation: 2789

There are several things going on here, and hopefully fixing them will help you get the test running and passing.

I can't recall for certain, but I believe GroovySpy is an old feature, that isn't used with Spock tests. Instead of using that to mock a domain class, you should be using the @Mock annotation before the class definition, to specify which domain classes you would like to mock.

While you can mock the domain classes, you'll also need to actually populate the in-memory database with those objects, either in a setup: block, or in a setup() method, if they are needed for multiple tests.

You mention creating mocks, and using the Spock Mock() method will create a mock of that object, but you don't want to use that for domain objects. It is more typically used for service classes that will be manually injected into your test class.

When saving a mock domain class, I would suggest including the parameters flush: true, failOnError: true, that way any validation that fails will be indicated immediately and on the appropriate line. Without this, you can get some strange errors to occur later in the tests.

I don't know what you're doing in the when: block, but you should not be doing an assertion with == at that point, do all of those in the then: block.

Given all of this, I think you test class should look more like this:

@Mock([User, Order])
class TestFun extends Specification {

    def setup() {
        // This object will have an id of 1, if you want a different id, you can either specify it, or create more objects
        User user = new User(first: "john", last: "doe").save(flush: true, failOnError: true)
        new Order(user: user).save(flush: true, failOnError: true)
    }

    def "test funtest"() {

        User user = new User(2).save()
        Order order = new Order(3).save()

        when:
        boolean result = service.testfun(1, 1)

        then:
        // Optionally you can just use "result" on the next line, as true will be assumed
        result == true
    }
}

Upvotes: 3

Related Questions