Let's say I have the following code:
class SomeController {
def fooService
def controllerMethod() {
def bar = fooService.doSomething()
// render bar to user - success case
}
def fooExceptionHandler(FooException e) {
// log, render error page, etc...
}
}
Based on grails' new declarative controller exception handling mechanism, if fooService.doSomething() throws an exception, grails will call fooExceptionHandler for me. Great.
Now when I unit test this method (test class uses the #TestFor(SomeController) annotation), this will fail saying that we expected a FooException but got nothing.
#Test(expected=FooException)
def doSomethingThrowsFooException() {
// override default service behavior, trigger a FooException
controller.fooService = [ doSomething: { throw new FooException() }]
controller.controllerMethod()
}
However, this works:
#Test
def doSomethingThrowsFooException() {
// override default service behavior, trigger a FooException
controller.fooService = [ doSomething: { throw new FooException() }]
controller.controllerMethod()
assert response.json == false
}
So the only way to test this method is by asserting that the response was what was expected but due to the declarative exception handling, this logic is now somewhere else (tested in isolation), not in the unit of code I am testing. Shouldn't my unit test only verify that the exception was propagated out of the controller method?
I would test the fooService integration with your controller in an integration test if possible, but I think that's only really testable sensibly in a functional test. You're really testing Grails - you're verifying that documented behavior occurs in your app.
If you add some code to throw an exception and then extra code to catch it and route it to the handler, you're just mocking out the stuff that Grails provides. Tests of that just test your mocking code, but have little to do with how well your code will work in production.
The unit test opportunity here is inside fooExceptionHandler. Test that given an exception, you do the right thing with it.
Related
I am new in Grails, I want to write unit tests for services using Spock. However I have the following issue.
import grails.transaction.Transactional
#Transactional
class BService {
boolean createB(){
return true
}
...
}
For this class I wrote the following test:
class BServiceTest extends Specification {
def "test createB"(){
given:
def service = new BService()
when:
def boolean temp
temp = service.createB()
then:
temp == true
}
}
The error I am getting when I run this test is the following:
java.lang.IllegalStateException: No transactionManager was specified. Using #Transactional or #Rollback requires a valid configured transaction manager. If you are running in a unit test ensure the test has been properly configured and that you run the test suite not an individual test method.
and it shows in GrailsTransactionTemplate.groovy:60
I would really appreciatie if anyone can give me a hint.
Add a #TestFor(BService) annotation to your unit test and use the instance of the service that it automatically provides. See http://grails.github.io/grails-doc/3.0.x/guide/testing.html for more information on testing.
Thank you ataylor for your reply. However I did a mistake, so I am now ansewring my own question.
First of all, the name conversion is wrong. When I create a service in grails there is automatically set a unit-test for this, so in that case I would have:
#TestFor(BService)
class BServiceSpec extends Specification {
def setup() {
User u = new User(1)
u.save()
}
def cleanup() {
}
def "test something"(){
}
}
In this case when I write a unit test, it runs.
The test that I had before was a functional test where I could not pass objects from the domain, so I had an error of the transactional manager.
Usually I was ending up writing test cases for a Domain by writing them for constraints and any custom methods(created by us in application) as we know we shouldn't test obvious.
But the time we started using coverage plugin, we found that our domains line of code is not fully covered which was due to gorm hooks(onInsert, beforeUpdate) that we never wrote test cases for.
Is there a way we can test these. One possible way that seems obvious but not suitable is to call another method(containing all code which was earlier in hooks) within these hooks and test that method only and be carefree for hooks.
Any solutions...
Edit
Sample code in domain that I want to unit-test:
class TestDomain{
String activationDate
def beforeInsert() {
this.activationDate = (this.activationDate) ?: new Date()//first login date would come here though
encodePassword()
}
}
How can I unit-test beforeInsert or I would end up writing integration test case?
Perhaps a unit test like:
import grails.test.mixin.TestFor
#TestFor(TestDomain)
class TestDomainSpec extends Specification {
def "test beforeSave"() {
given:
mockForConstraintsTests(TestDomain)
when:
def testDomain = new TestDomain().save(flush:true)
then:
testDomain.activationDate != null
}
}
I have a controller class with this code:
List<MultipartFile> files = []
List<String> convertedContents = []
def convertedFiles = [:]
try {
params.myFile.each {
if (((MultipartFile) it.value).empty) {
throw new NoUploadedFileException('Break .each closure due to empty input.')
}
files.add((MultipartFile) it.value)
}
} catch (NoUploadedFileException e) {
redirect uri: request.getHeader('referer')
return
}
convertedContents = converterService.convertToBase64(files)
(code omitted)
I also have a test:
def "sampleTest"() {
when:
controller.sendFax()
then:
thrown(NoUploadedFileException)
response.redirectedUrl == 'index.gsp'
}
What I'm trying to test is that my Controller would throw a "NoUploadedFileException" when no file are uploaded and the submit button is clicked.
This is the error:
Running 1 unit test... 1 of 1
| Failure: sampleTest(com.synacy.HomeControllerSpec)
| Expected exception com.synacy.NoUploadedFileException, but got
java.lang.NullPointerException
at org.spockframework.lang.SpecInternals.thrownImpl(SpecInternals.java:79)
at com.synacy.HomeControllerSpec.throws NoUploadedFileException and returns to the
same page when no file is uploaded(HomeControllerSpec.groovy:36)
Caused by: java.lang.NullPointerException: Cannot invoke method convertToBase64() on
null object
at com.synacy.HomeController.sendFax(HomeController.groovy:43)
at com.synacy.HomeControllerSpec.sampleTest(HomeControllerSpec.groovy:33)
It seems that it isn't going through the try-catch block, or if it is, the implementation is not working.
For a Unit test, in either setup: or when: you have to create the environment your controller is acting in.
Populate the request with whatever headers/params/data are appropriate -- headers, parameters, payload, whatever. Setup the grails application configuration with whatever is appropriate if needed.
Your NPE says that you aren't hitting the controller code you are trying to test. The NPE is because your controller doesn't have a converterService assigned/injected (it's a Unit test). You haven't set any params for your controller, so you aren't entering your .each block of code.
Perhaps controller.params.myFile = new MultipartFile() // just guessing here
Abbreviated example from one of my tests (I want to test the json response, so I have to format the request appropriately):
#TestFor(DirectoryController)
#Mock([SpringSecurityService,ConfigurationService,ConfigurationDomain,EntryDomain])
class DirectoryControllerSpec extends Specification
def "test getDirectorySources"() {
setup:
// not testing authentication, so just return true
SpringSecurityUtils.metaClass.static.ifAllGranted = { String role ->
return true
}
// mock the data to be returned from the configurationService call
def configuration = new ConfigurationDomain(id:2,name:'Mock Config',entries:[])
def typeEntry = new EntryDomain(key:'ldap01.type',value:'ad')
configuration.entries << typeEntry
def nameEntry = new EntryDomain(key:'ldap01.name',value:'LDAP01')
configuration.entries << nameEntry
// mock the configurationService
def mockConfigurationService = mockFor(ConfigurationService, true) // loose mock
mockConfigurationService.demand.getConfigurationById() { Long id ->
return configuration
}
controller.configurationService = mockConfigurationService.createMock()
when:
// setup the request attributes
request.setContentType('application/json')
request.method = 'GET'
request.addHeader('Accept','application/json')
controller.params.id = "2"
controller.getDirectorySources()
then:
response.getText() == '{"sources":[{"key":"ldap01","name":"LDAP01","type":"ad"}]}'
cleanup:
// reset the metaClass
SpringSecurityUtils.metaClass = null
}
}
Mock any services used by the controller; you aren't testing your converterService, so that should be mocked, and return a known value so you know what the controller should do in response to the data returned from the service.
In short, in a Unit test, you should control everything not in the immediate controller code.
You should mock your service so your test should look like:
#TestFor(MultipleFileUploadController)
#Mock([ConverterService])
class MultipleFileUploadControllerSpec extends Specification {
...
}
But your test will not pass because you handle NoUploadedFileException in controller, so it will not be caught anywhere in tests, so you should have fail with
Expected exception com.stackoverflow.NoUploadedFileException, but no exception was thrown
Remove the line thrown(NoUploadedFileException) from test.
Try mocking your service above your test class as:
#TestFor(MultipleFileUploadController)
#Mock([ConverterService])
This will solve your issue as spock will mock the ConverterService class for you and you can call its methods. Please keep in mind that each and every domains and services you use in your methods should be mocked in your test. Hope it will help.
I use the Grails Spring Security Plugin for my project and now want to unit test my code. I have the following code in my controller:
def index() {
redirect action: 'show', params: [id: springSecurityService.currentUser.id]
}
My Test Class has the following code:
void testIndex() {
controller.index()
assert "/user/list" == response.redirectedUrl
}
This test fails:
| Running 8 unit tests... 1 of 8
| Failure: testIndex(xxx.UserControllerTests)
| java.lang.NullPointerException: Cannot get property 'currentUser' on null object
at xxx.UserController.index(UserController.groovy:12)
at xxx.UserControllerTests.testIndex(UserControllerTests.groovy:19)
How can I authenticate a spring security user in a test case? How would you write the unit test?
You have to use functional tests for security. Unit tests use mocking but don't have plugins available, or a real request. Spring Security is implemented with a filter chain, so you need a real running server. If you use mocks, you're just testing the mocking.
For something this simple I wouldn't bother with complicated mocks, a straightforward
controller.springSecurityService = [currentUser:[id:1]]
would be sufficient.
It appears that your reference to springSecurityService is null. As long as you have a field in your controller named springSecurityService, it should be injected. Are you using it as a local variable only in your index method and did not declare it as a field?
My UserController is as follows:
class UserController {
/**
* Dependency injection for the springSecurityService.
*/
def springSecurityService
....
}
UPDATE
Based on your comments to this answer, you did declare a springSecurityService field in your controller. I took my working application and tried a test that mirrors yours with my controller method:
#TestFor(UserController)
class UserControllerTests {
void testSomething() {
controller.register()
}
}
I got a NullPointerException as well. From Burt's answer, (I did not know this), I think the springSecurityService instance is null in the contexts of the Unit Test execution.
I am trying to test a Controller
that has a Command object with data binding.
The Command Object has a Service injected into it.
But When I try test the command object the injected service method
is never found as it is never "injected"
Is there a way to mock a service inside a command object?
Test method
void testLoginPasswordInvalid() {
mockRequest.method = 'POST'
mockDomain(User, [new User(login:"freddy", password:"realpassword")])
mockLogging(UserService) // userService mocked
MockUtils.prepareForConstraintsTests(LoginCommand)
def userService = new UserService()
def user = userService.getUser("freddy")//Gets called and returns the mockDomain
assert userService.getUser("freddy")//Passes
def cmd = new LoginCommand(login:"freddy", password:"letmein")
cmd.validate() // Fails (userService is nevr injected)
controller.login(cmd)
assertTrue cmd.hasErrors()
assertEquals "user.password.invalid", cmd.errors.password
assertEquals "/store/index", renderArgs.view
}
The getUser() method of the userService isn't found
Cannot invoke method getUser() on null object
java.lang.NullPointerException: Cannot invoke method getUser() on null object
Code
The login method of the controller being called,
def login = { LoginCommand cmd ->
if(request.method == 'POST') {
if(!cmd.hasErrors()){
session.user = cmd.getUser()
redirect(controller:'store')
}
else{
render(view:'/store/index', model:[loginCmd:cmd])
}
}else{
render(view:'/store/index')
}
}
The Command Object has a "userService" injected into it.
The validator calls this userService to find a user
class LoginCommand {
def userService
String login
String password
static constraints = {
login blank:false, validator:{ val, cmd ->
if(!cmd.userService.getUser()){
return "user.not.found"
}
}
}
The userService.getUser() looks like this.
class UserService {
boolean transactional = true
User getUser(String login) {
return User.findByLogin(login)
}
}
Service injection is done using Spring autowire-by-name. (Grep the Grails source tree for autowire to find a nice code fragment you can use to get it to autowire your controllers for you in integration tests.) This only functions in integration tests, where there's a Spring application context around that has the beans that can be injected.
In unit tests, you have to do this yourself since there's no Spring-land surrounding your stuff. This can be a pain, but gives you some benefits:
1) It's easy to inject mock versions of services - for example, using an Expando - in order to more closely specify the behavior of your controller's collaborating services, and to allow you to test only the controller logic rather than the controller and service together. (You can certainly do the latter in a unit test as well, but you have the choice of how to wire it up.)
2) It forces you to be explicit about the dependencies of your controller - if you depend on it, your tests will show it. This makes them a better specification for the behavior of your controller.
3) You can mock only the pieces of external collaborators your controller depends on. This helps your tests be less fragile - less likely to need to change when things change.
Short answer: your test method needs a cmd.userService = userService line.
What John says is on the mark. One example might be:
def mockUsers = [new User(login:"freddy", password:"realpassword")]
mockDomain(User, mockUsers)
def userService = [getUser:{String login -> mockUsers[0]}] as UserService
def cmd = new LoginCommand (/*arguments*/)
cmd.userService = userService
You can lookup other ways to mock objects at http://groovy.codehaus.org/Groovy+Mocks