wureka
wureka

Reputation: 865

How to use spock to test a service with real transactions in Grails application?

I am using Grails 2.4.4 and want to apply spock tests in my project.

Below is the MemberService:

@Transactional
class MemberService {

    def lastMember = null

    def login (userId, password) {

        def member = Member.findByLoginEmail(userId)
        if(!member) return LoginResult.NO_SUCH_MEMBER
        if (member.isLocked) return LoginResult.MEMBER_IS_LOCKED

        log.debug("member.password is [${member.passwd}], input password is [${password}]")

        if (!member.passwd.equals(password)) return LoginResult.PASSWORD_ERROR

        switch(member.validateResult) {
            case "FAILED":
                return LoginResult.VAILDATE_FAILED
            case "WAIT":
                return LoginResult.WAIT_VALIDATE
        }

        member.lasLoginTime = new Timestamp(System.currentTimeMillis())
        member.lastLoginPlatform = "WEB"
        member.loginFailCount = 0
        member.save()
        lastMember = member
        return LoginResult.SUCCESS
    }

    enum LoginResult {
        SUCCESS,
        NO_SUCH_MEMBER,
        PASSWORD_ERROR,
        MEMBER_IS_LOCKED,
        VAILDATE_FAILED,
        WAIT_VALIDATE
    }

    enum ValidateResult {
        SUCCESS,
        FAILED,
        WAIT
    }
}

MemberServiceSpec is as below:

@TestFor(MemberService)
@Mock(Member)
class MemberServiceSpec extends Specification {
    def memberService

    def setup() {
        memberService = new MemberService()
    }

    void "test something"() {

        when:
            println "service is ${service}"
            def result = memberService.login("[email protected]", "123456")
            println "result is ${result}"
        then:
            result == MemberService.LoginResult.SUCCESS
    }
}

The test result is as below:

Testing started at 15:15 ...
|Loading Grails 2.4.4
|Configuring classpath
.
|Environment set to test
....................................
|Running without daemon...
..........................................
|Compiling 1 source files
.
|Running 2 unit tests...|Running 2 unit tests... 1 of 2
--Output from test something--
1. setup
service is cusine_market.MemberService@54c425b1
Enter MemberService.login----------------userId=[[email protected]]
result is NO_SUCH_MEMBER
Failure:  |
test something(cusine_market.MemberServiceSpec)
 |
Condition not satisfied:
result == MemberService.LoginResult.SUCCESS
|      |
|      false
NO_SUCH_MEMBER
    at cusine_market.MemberServiceSpec.test something(MemberServiceSpec.groovy:32)
Condition not satisfied:

result == MemberService.LoginResult.SUCCESS
|      |
|      false
NO_SUCH_MEMBER

Condition not satisfied:

result == MemberService.LoginResult.SUCCESS
|      |
|      false
NO_SUCH_MEMBER

    at cusine_market.MemberServiceSpec.test something(MemberServiceSpec.groovy:32)

|Completed 1 unit test, 1 failed in 0m 6s
.Tests FAILED 

I confirm that the user exists in database.

Could anyone tell me why MemberService can not find the user "[email protected]" in database? I also tried the below line

    memberService = Mock(MemberService);

The result is the same. However, if I run-app, the service does find the user.

Upvotes: 0

Views: 1298

Answers (1)

Marcin Świerczyński
Marcin Świerczyński

Reputation: 2312

Mocking

In your test you're testing MemberService, which means you don't need to mock it. Just refer to it via service.

Test Data

This looks like unit test, not the integration one. So it doesn't use your database at all.

What you need to do is to create the Member instance manually in test. You have it in your @Mock annotation, which is good.

Now, create the object, preferably in a given block:

void "test something"() {
    given:
        new Member(loginEmail: '[email protected]', password: '123456', ...).save(failOnError: true, flush: true)

    when:
        println "service is ${service}"
        def result = service.login("[email protected]", "123456")
        println "result is ${result}"
    then:
        result == MemberService.LoginResult.SUCCESS
}

I added failOnError: true to make sure the object has actually been created. You - of course - need to provide all required properties in Member constructor and make sure the data you provide correspond to the one you provide to login method.

Also, you need to ensure the Member object is initialized in a way that fulfills the path you want to reach. For example, if you want to reach LoginResult.SUCCES state, you need to set member.isLocked to false, etc.

BONUS

When you get this test to work, you may want to take a look into Build Test Data Plugin. It makes creating test data - like your Member object - much easier.

Upvotes: 2

Related Questions