Metaclass delete domain method in Grails 2 unit test - unit-testing

I am trying to test the throwing of an exception in my unit test. I tried to metaclass the delete method, but it doesn't want to stick. Can you tell from the code what I'm doing wrong?
Unit Test code:
#TestFor(ProductController)
#TestMixin(DomainClassUnitTestMixin)
class ProductControllerTests {
void testDeleteWithException() {
mockDomain(Product, [[id: 1, name: "Test Product"]])
Product.metaClass.delete = {-> throw new DataIntegrityViolationException("I'm an exception")}
controller.delete(1)
assertEquals(view, '/show/edit')
}
Controller action code:
def delete(Long id) {
def productInstance = Product.get(id)
if (!productInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'product.label', default: 'Product'), id])
redirect(action: "list")
return
}
try {
productInstance.delete(flush: true)
flash.message = message(code: 'default.deleted.message', args: [message(code: 'product.label', default: 'Product'), id])
redirect(action: "list")
}
catch (DataIntegrityViolationException e) {
flash.message = message(code: 'default.not.deleted.message', args: [message(code: 'product.label', default: 'Product'), id])
redirect(action: "show", id: id)
}
}
When I run the test, productInstance.delete(flush: true) does not throw the exception I'm expecting. Instead it redirects to action: "list". Does anyone know how to override the Product.delete() method so I can force the exception?

You're mocking delete without any arguments, but your controller calls delete(flush: true). Try mocking out delete(Map) like this:
Product.metaClass.delete = { Map params ->
throw new DataIntegrityViolationException("...")
}

Related

Grails how to perform unit test on exceptions

I am trying to test that the account expired exception.
def authfail() {
String msg = ''
def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
// println("print exception: ${exception} | ${session} | ${springSecurityService.getCurrentUser()}")
if (exception) {
if (exception instanceof AccountExpiredException) {
msg = message(code: 'springSecurity.errors.login.expired')
}
else if (exception instanceof CredentialsExpiredException) {
msg = message(code: 'springSecurity.errors.login.passwordExpired')
}
else if (exception instanceof DisabledException) {
msg = message(code: 'springSecurity.errors.login.disabled')
}
else {
msg = message(code: 'springSecurity.errors.login.fail')
}
}
if (springSecurityService.isAjax(request)) {
render([error: msg] as JSON)
}
else {
flash.message = msg
redirect action: 'auth', params: params
}
}
I tried writing the test case above before realising i was stuck as I have no idea how to trigger an expired login so that the unit test criteria of having the AccountExceptionExpired exception thrown be fulfilled.
void "test authFail"() {
when:
session."${WebAttributes.AUTHENTICATION_EXCEPTION}" = new AccountExpiredException( 'This account has expired' )
def logexp = controller.authfail()
then:
logexp == 'springSecurity.errors.login.expired'
when:
session."${WebAttributes.AUTHENTICATION_EXCEPTION}" = new CredentialsExpiredException( 'This credentials have expired' )
def passexp = controller.authfail()
then:
passexp == 'springSecurity.errors.login.passwordExpired'
when:
session."${WebAttributes.AUTHENTICATION_EXCEPTION}" = new DisabledException( 'The account is disabled' )
def logdis = controller.authfail()
then:
logdis == 'springSecurity.errors.login.disabled'
when:
session."${WebAttributes.AUTHENTICATION_EXCEPTION}" = new UnsupportedOperationException( 'Sorry, we were not able to find a user with that username and password.' )
def logfail = controller.authfail()
then:
logfail == 'springSecurity.errors.login.fail'
when:
controller.authfail()
then:
1 * springSecurityService.isAjax( _ ) >> true
controller.response.json == [error :'springSecurity.errors.login.fail']
}
}
The following will test the majority of your method:
import grails.plugin.springsecurity.SpringSecurityService
import grails.test.mixin.TestFor
import org.springframework.security.authentication.AccountExpiredException
import org.springframework.security.authentication.CredentialsExpiredException
import org.springframework.security.authentication.DisabledException
import org.springframework.security.web.WebAttributes
import spock.lang.Specification
#TestFor(YourController)
class YourControllerSpec extends Specification {
def springSecurityService = Mock( SpringSecurityService )
void setup() {
controller.springSecurityService = springSecurityService
}
void "test authFail"() {
given:
session."${WebAttributes.AUTHENTICATION_EXCEPTION}" = new AccountExpiredException( 'This account has expired' )
when:
controller.authfail()
then:
flash.message == 'springSecurity.errors.login.expired'
when:
session."${WebAttributes.AUTHENTICATION_EXCEPTION}" = new CredentialsExpiredException( 'This credentials have expired' )
controller.authfail()
then:
flash.message == 'springSecurity.errors.login.passwordExpired'
when:
session."${WebAttributes.AUTHENTICATION_EXCEPTION}" = new DisabledException( 'The account is disabled' )
controller.authfail()
then:
flash.message == 'springSecurity.errors.login.disabled'
when:
session."${WebAttributes.AUTHENTICATION_EXCEPTION}" = new UnsupportedOperationException( 'Bad stuff' )
controller.authfail()
then:
flash.message == 'springSecurity.errors.login.fail'
when:
controller.authfail()
then:
1 * springSecurityService.isAjax( _ ) >> true
response.json == [error :'springSecurity.errors.login.fail']
}
}
The session is just a map to which we add a key of the string constant and a value of the exception.
For all tests bar the final one we're falling through to your final else block, in the final test we return true for `isAjax'.
While this is not Grails, it is SpringBoot 2.0.
If the failureHandler is exposed as a bean, one can just spy on it.
#SpyBean
AuthenticationFailureHandler failureHandler;
and simply verify that the exception has been thrown
Mockito.verify(failureHandler).onAuthenticationFailure(
any(),
any(),
any(AccountExpiredException.class)
);
A simple test can look like this:
#Test
public void accountExpired() throws Exception {
doReturn(user
.username("expired")
.accountExpired(true)
.build()
).when(userDetailsService).loadUserByUsername(any(String.class));
mvc.perform(
MockMvcRequestBuilders.post("/login")
.param("username", "expired")
.param("password", "password")
)
.andExpect(status().is4xxClientError())
.andExpect(unauthenticated())
;
Mockito.verify(failureHandler).onAuthenticationFailure(
any(),
any(),
any(AccountExpiredException.class)
);
}
All samples at https://github.com/fhanik/spring-security-community/

Grails 2.4.4 controller test on save() return empty model

I have a test for a PersonController, it just executes save() with no params, so an invalid person is created. It should return the invalid person in the model and show the create view. But the model is empty.
The test:
import org.junit.*
import grails.test.mixin.*
#TestFor(PersonController)
#Mock(Person)
class PersonControllerTests {
...
void testSave() {
controller.save() // creates invalid person, redirects to create
assert model.personInstance != null
assert view == '/person/create'
response.reset()
populateValidParams(params)
controller.save()
assert response.redirectedUrl == '/person/show/1'
assert controller.flash.message != null
assert Person.count() == 1
}
...
}
The controller:
class PersonController {
...
def save() {
def personInstance = new Person(params)
if (!personInstance.save(flush: true)) {
render(view: "create", model: [personInstance: personInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'person.label', default: 'Person'), personInstance.id])
redirect(action: "show", id: personInstance.id)
}
...
}
The output:
junit.framework.AssertionFailedError: Assertion failed:
assert model.personInstance != null
| | |
[:] null false
at demographic.PersonControllerTests.testSave(PersonControllerTests.groovy:43)
How can I get the right model?
Is this the expected behavior or is this a Grails bug?
The test is failing (rightly so, I believe) because of the HTTP method restrictions in the controller, i.e. the line:
static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
Set the HTTP method in the test, and the test passes:
void testSave() {
controller.request.method = 'POST'
controller.save() // creates invalid person, redirects to create
...

Grails Unit Test Failing

So, I am starting learning groovy & grails. I am trying to write unit tests for my controller like this:
void testSave() {
params.productName = 'ProdName'
params.productBarCode = '123'
params.productStore = 'ProdStore'
def response = controller.save()
assert response.productInstance.productName == 'ProdName'
}
and this is the controller action
def save() {
def productInstance = new Product(params)
if (!productInstance.save(flush: true)) {
render(view: "create", model: [productInstance: productInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'product.label', default: 'Product'), productInstance.id])
redirect(action: "show", id: productInstance.id)
}
and this is the exception it throws when 'test-app'
groovy.lang.MissingMethodException: No signature of method: xxx.Product.save() is applicable for argument types: () values: []
Possible solutions: save(), save(boolean), save(java.util.Map), wait(), any(), wait(long)
at xxx.ProductController.save(ProductController.groovy:59)
at xxx.ProductControllerTests.testSave(ProductControllerTests.groovy:35)
I am sorry if this question is too naive. Please help
Thanks
Until the domain instance is mocked in the test class, it won't be able to recognize dynamic methods like save() on the domain class.
Use #Mock(Product) at class level in the test class.

Grails 2.0: POST and flash.message

Having the following in your controller:
static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
will cause the save() action for example to set an empty flash map. I.e. the following test will fail because flash is [:] after the action returns and message is null:
Controller:
static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
[...]
def save = {
flash.message = "Saved"
}
Test:
void testSave() {
controller.save()
assert null != flash.message
}
Is there any reason for that or is that a bug in Grails 2.0?
The reason flash is empty is that you have defined save() to use only POST method. That's what grails do - it doesn't allow you to access save() with GET. Your complete test should look like this:
void testSaveWithGet() {
controller.save()
assert response.status == HttpServletResponse.SC_METHOD_NOT_ALLOWED
}
void testSaveWithPost() {
request.method = "POST"
controller.save()
assert flash.message == "Saved"
}

Grails 2.0: Issue testing flash.message

I have a unit test for my UserController but since upgrading to Grails 2.0, the flash variable always seems to return an emtpy map with no message.
Here are some code snippets of the UserControllerTests:
#TestFor(UserController)
#Mock(User)
class UserControllerTests {
...
void testSaveSucceeds() {
params.userName = 'Joe'
...
controller.save()
assert null != flash.message
assert '/user/list' == response.redirectedUrl
}
}
In UserController:
def save = {
def userInstance = new User(params)
if (userInstance.validate()) {
flash.message = message(code: 'default.created.message', args: [userInstance.userName ])
...
}
But my test result is as follows:
assert null != flash.message
| | |
| [:] null
false
I have tried as an integration test as well because otherwise the response was null as weill but it did not fix the flash issue. The same problem also exists with view and model.
What am I missing? Any help highly appreciated.
Regards
Jonas
EDIT:
Here's a weird scenario:
My controller has the following:
def test = {
flash.message = "Message"
}
def save = {
flash.message = "Message"
}
My Test looks like that:
void testSaveSucceeds() {
controller.save()
println ">>> ${flash}"
controller.test()
println ">>> ${flash}"
}
The output like that:
>>> [:]
>>> [message:Message]
Interesting to mention is also that the debugger in IntelliJ stops at a breakpoint in the test() action but not in save()
HOW can that be????
Regards
Jonas
For me it means that userInstance.validate() return false ie the validation failed.