With grails 2.5.1, I only created a service with a simple property initialized through a constructor (#PostConstruct also)
Any unit test that read this property through a service method get a NullPointerException
Here's how to reproduce:
grails> create-app acme
grails> create-service DataService
acme/DataService.groovy
#Transactional
class DataService {
def data = null;
#PostConstruct
def setup() {
if (data == null) {
println("Initializing the service ...")
data = "<DATA>"
}
}
def serviceMethod() {
setup()
}
def getDataLength() {
println("Getting data length ...")
return data.length(); // Line 26
}
}
acme/DataServiceSpec.groovy
#TestFor(DataService)
class DataServiceSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "test data"() {
given:
int datalen = service.getDataLength()
expect:
datalen > 0
}
}
Running the test ....
grails> test-app -unit -echoOut
The output ...
|Running 2 unit tests...
|Running 2 unit tests... 1 of 2
--Output from test data--
Initializing the service ...
Getting data length ...
Failure: |
test data(acme.DataServiceSpec)
|
java.lang.NullPointerException: Cannot invoke method length() on null object
at acme.DataService.getDataLength(DataService.groovy:26)
at acme.DataServiceSpec.test data(DataServiceSpec.groovy:20)
|Completed 1 unit test, 1 failed in 0m 2s
.Tests FAILED
The log shows the property initialization step and the NullPointerException for the property.
The questions are:
Why the 'data' property becomes null ?
There's a service lifecycle setting the property to null ?
Is the running test using a different DataService instance from the initialized instance ?
Have you seen Grails unit test fails on #PostConstruct?
In unit test and with #TestFor annotation #PostConstruct is not called. You can create construct for test or call setup method.
Related
Using Grails 3.2.8 and the Spock framework for testing, given the following controller class:
class SomeController {
def doSomething() {
// do a few things, then:
someOtherMethod()
}
protected void someOtherMethod() {
// do something here, but I don't care
}
}
How can I test the doSomething() method to make sure someOtherMethod() is called exactly once?
This is my attempt that failed:
#TestFor(SomeController)
class SomeControllerSpec extends Specification {
void "Test that someOtherMethod() is called once inside doSomething()"() {
when:
controller.doSomething()
then:
1 * controller.someOtherMethod(_)
}
}
Error message:
Too few invocations for:
1 * controller.someOtherMethod(_) (0 invocations)
Note: Imports have been omitted to focus on the problem at hand
You can't do that as controller is not a mocked object. Instead, you need to use metaclass like this:
#TestFor(SomeController)
class SomeControllerSpec extends Specification {
void "Test that someOtherMethod() is called once inside doSomething()"() {
given:
Integer callsToSomeOtherMethod = 0
controller.metaClass.someOtherMethod = {
callsToSomeOtherMethod++
}
when:
controller.doSomething()
then:
callsToSomeOtherMethod == 1
}
}
When do grails unit test with Spock, can't auto inject a service instance to domain.
Below is my code.
Service:
class HiService {
public HiService(){
println "Init HiService," + this.toString()
}
def sayHi(String name){
println "Hi, ${name}"
}
}
Domain:
class User {
public User(){
if (hiService == null){
println "hiService is null when new User(${name})"
}
}
String name
def hiService
def sayHi(){
println "Before use hiService " + hiService?.toString()
hiService.sayHi(name)
println "End use hiService" + hiService?.toString()
}
}
TestCase:
#TestFor(HiService)
#Mock([User])
class HiServiceTest extends Specification {
def "test sayHi"() {
given:
def item = new User( name: "kitty").save(validate: false)
when: "Use service method"
item.sayHi()
then : "expect something happen"
assertEquals(1, 1)
}
}
The following was console log:
--Output from test sayHi--
Init HiService,test.HiService#530f5e8e
hiService is null when new User(null)
Before use hiService null
| Failure: test sayHi(test.HiServiceTest)
| java.lang.NullPointerException: Cannot invoke method sayHi() on null object
at test.User.sayHi(User.groovy:17)
at test.HiServiceTest.test sayHi(HiServiceTest.groovy:20)
The service initialized, but can't inject to domain. But when run app directly, service will auto-inject to domain
If you wan't autowiring it needs to be an integration test. If using Grails 3 then annotate with #Integration, if grails 2 then extend IntegrationSpec.
See: http://docs.grails.org/latest/guide/testing.html#integrationTesting
#Mock([User, HiService])
class HiServiceTest extends Specification {
def "test sayHi"() {
// ....
}
}
Since you are writing unit tests, your service will not be autowired. Also as you are unit testing the User class object, you should write the test in UserSpec (instead of UserServceTest; Suffixing Spec is the convention in Spock). Now you can mock the HiService instead like this:
class UserSpec extends Specification {
def "User is able to say hi"() {
given:
User user = new User(name: 'bla bla')
and: "Mock the user service"
def hiService = Mock(HiService)
user.hiService = hiService
when:
user.sayHi()
then:
1 * sayHiService.sayHi(user.name)
}
}
I'm testing a Grails application but i get this error message:
I'm trying to do unit test. My Grails version is 2.4.3
Error executing script TestApp: java.lang.ClassNotFoundException: grails.plugin.spock.
That's my code:
#TestFor(TrackEmailController)
#Mock(TrackEmail)
class TrackEmailUnit2Spec extends Specification {
def setup() {
}
def cleanup() {
}
void "test something"() {
}
void 'test mandrillEventsEndPoint'() {
when:
controller.mandrillEventsEndPoint()
then:
response.text == 'mandrill Events End Point'
}
}
Can you be a little more specific on how you are running your test cases and how you have integrated spock dependencies? Or perhaps provide a github link to replicate the issue?
The error was on integration test of a service. Yes, we're currently doing integration test on our service as a workaround since we cannot test the services via unit test due to userType mapping.
Regarding userType: Our Domain uses interface as properties. In addition findByinterface won't work on grails.
see below:
class Domain {
SomeProperty someProperty
String otherProperty
}
public interface SomeProperty {
//methods
}
enum Enum implements SomeProperty {
NAME,
OTHERNAME
//implementation of methods
}
class Domain can handle different kinds of enum implementing the SomeProperty inteface.
Database won't know what particular value should it saved so we go with the userType. below is the Domain with the mapping
class Domain {
SomeProperty someProperty
String otherProperty
}
static mapping = {
id generator: 'sequence', column: 'id', params: [sequence: 'domain_sequence']
someProperty type : userType, {
column name: "someProperty", sqlType: "varchar", length: 255
}
}
Updated:
Code being tested:
class ServiceBeingTested {
AnotherServiceInsideServiceBeingTested anotherServiceInsideServiceBeingTested //or def anotherServiceInsideServiceBeingTested
public void methodBeingTested(param1, param2, param3) {
Object object = privateMethod(..., ...) //private method contains no other service calls. just pure logic
anotherServiceInsideServiceBeingTested.voidMethod1(....)
anotherServiceInsideServiceBeingTested.voidMethod2(....)
}
}
My integration test:
class ServiceBeingTestedIntegrationSpec extends IntegrationSpec {
ServiceBeingTested serviceBeingTested = new ServiceBeingTested()
AnotherServiceInsideServiceBeingTested anotherServiceInsideServiceBeingTested
void setUp () {
anotherServiceInsideServiceBeingTested = Mock()
serviceBeingTested.anotherServiceInsideServiceBeingTested = anotherServiceInsideServiceBeingTested
}
void cleanup () {
//code here
}
void "methodBeingTested should invoke the 2 service method call"() {
given:
//initialize data here
//code
when:
serviceBeingTested.methodBeingTested(param1, param2, param3)
then:
1 * anotherServiceInsideServiceBeingTested.voidMethod1(....)
1 * anotherServiceInsideServiceBeingTested.voidMethod2(....)
}
}
StackTrace:
| Too few invocations for:
1 * anotherServiceInsideServiceBeingTested.voidMethod2(....) (0 invocations)
Unmatched invocations (ordered by similarity):
1 * anotherServiceInsideServiceBeingTested.this$3$voidMethod2(....)
The other service call with same anotherServiceInsideServiceBeingTested -i.e anotherServiceInsideServiceBeingTested.voidMethod1 was correctly invoked. I tried changing the parameters in test for voidMethod2 to wildcards but will still result to this error.
Thanks
In integration test cases, you don't need to mock any of the services as these are available by default as the full application is loaded.
Though, in unit test, you may use #TestFor annotation and specify the service which you want to test, this will inject a service variable which would be available through you specification / test class.
#TestFor(Service)
class ServiceBeingTestedIntegrationSpec extends Specification {
ServiceBeingTested serviceBeingTested = new ServiceBeingTested()
void setUp () {
serviceBeingTested.service = service
}
void cleanup () {
//code here
}
void "methodBeingTested should invoke the 2 service method call"() {
given:
//initialize data here
//code
when:
serviceBeingTested.methodBeingTested(param1, param2, param3)
then:
1 * service.voidMethod1(....)
1 * service.voidMethod2(....)
}
}
Also, in case above you could also try to stub your methods in setup after mocking these to avoid any other service called by your service which will throw NullPointerException and eventually your test would fail in case of unit tests.
example of stubbing could be:
1*service.voidMethod1 >> {
//some code setting up any data for you.
}
1*service.voidMethod2 >> {
//some code setting up any data for you.
}
If you use stubbing like above you can simply write down unit test cases as well.
I cannot figure out why my integration tests here are throwing exceptions.
Integration Test ##
package sample
import grails.test.mixin.*
import org.junit.*
/**
* See the API for {#link grails.test.mixin.web.ControllerUnitTestMixin} for usage instructions
*/
#TestFor(UserController)
class UserControllerTests extends GroovyTestCase {
User user
UserController uc
void setUp() {
//Save a User
user = new User(userName: "User1", firstName: "User1FN", lastName: "User1LN")
user.save()
//Set up UserController
uc = new UserController()
}
void tearDown() {
user.delete()
}
/**
* Test the UserController.handleLogin action.
*
* If the login succeeds, it will put the user object into the session.
*/
void testHandleLogin() {
//Setup controller paramaters
uc.params.userName = user.userName
//Call the action
uc.handleLogin()
//if the action functioned correctly, it put a user object into the session
def sessUser = uc.session.user
assert sessUser
assertEquals("Expected ids to match", user.id, sessUser.id)
//And the user was redirected to the Todo Page
assertTrue uc.response.redirectedUrl.startsWith("/todo")
}
/**
* Test the UserController.handleLogin action.
*
* If the login fails, it will redirect to login and set a flash message.
*
*/
void testHandleLoginInvalidUser() {
//Setup controller parameters
uc.params.userName = "Invalid_Name"
//Call the action
uc.handleLogin()
assertEquals "/user/login", uc.response.redirectedUrl
def message = uc.flash.message
assert message
assert message.startsWith("User not found")
}
/*
* Test the UserController.logout action
*
* If the logout action succeeds, it will remove the user object from the session.
*/
void testLogout (){
//Make a user logged into session
uc.session.user = user
//Call the action
uc.logout()
def sessUser = uc.session.user
assertNull ("Expected session user to be null", sessUser)
assertEquals "/user/login", uc.response.redirectedUrl
}
}
Console output for test
| Loading Grails 2.2.3
| Configuring classpath.
| Environment set to test.....
| Packaging Grails application.....
| Packaging Grails application.....
| Compiling 1 source files.
| Running 3 integration tests... 1 of 3
| Running 3 integration tests... 2 of 3
| Failure: testHandleLoginInvalidUser(sample.UserControllerTests)
| java.lang.NullPointerException: Cannot invoke method getBean() on null object
at grails.test.mixin.web.ControllerUnitTestMixin$_mockController_closure3.doCall(ControllerUnitTestMixin.groovy:304)
at grails.test.mixin.web.ControllerUnitTestMixin.mockController(ControllerUnitTestMixin.groovy:311)
| Failure: testHandleLoginInvalidUser(sample.UserControllerTests)
| java.lang.NullPointerException: Cannot invoke method delete() on null object
at sample.UserControllerTests.tearDown(UserControllerTests.groovy:25)
| Running 3 integration tests... 3 of 3
| Failure: testLogout(sample.UserControllerTests)
| java.lang.NullPointerException: Cannot invoke method getBean() on null object
at grails.test.mixin.web.ControllerUnitTestMixin$_mockController_closure3.doCall(ControllerUnitTestMixin.groovy:304)
at grails.test.mixin.web.ControllerUnitTestMixin.mockController(ControllerUnitTestMixin.groovy:311)
| Failure: testLogout(sample.UserControllerTests)
| java.lang.NullPointerException: Cannot invoke method delete() on null object
at sample.UserControllerTests.tearDown(UserControllerTests.groovy:25)
| Failure: sample.UserControllerTests
| java.lang.NullPointerException: Cannot invoke method isActive() on null object
at grails.test.mixin.support.GrailsUnitTestMixin.shutdownApplicationContext(GrailsUnitTestMixin.groovy:234)
| Completed 3 integration tests, 5 failed in 980ms
You're using unit test annotations in an integration test - that will cause lots of problems. In general when doing integration tests you extend GroovyTestCase if you want to use JUnit3-style tests, or nothing and use JUnit4 annotations, or use Spock and extend IntegrationSpec.
As for the NPEs, whether you're using proper unit tests or integration tests, you need to manage the controller's dependencies yourself since you explicitly create it with new and don't access it as a pre-wired Spring bean. But the integration test does support dependency injection, so just add fields in your test for whatever you need in the controller, and in setUp or in individual methods you can set those beans in the controller, e.g.
class UserControllerTests extends GroovyTestCase {
def grailsApplication
def someSpringBean
def someOtherSpringBean
private UserController uc = new UserController()
protected void setUp() {
super.setUp()
user = new User(userName: "User1", firstName: "User1FN", lastName: "User1LN").save()
//Set up UserController
uc.applicationContext = grailsApplication.mainContext
uc.someSpringBean = someSpringBean
uc.someOtherSpringBean = someOtherSpringBean
}
...
Also note that you don't need to cleanup your data in tearDown() - integration tests run in a transaction that's rolled back at the end of the test method.