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.
Related
I am unit testing a Grails controller method that internally creates an user instance. User domain class uses the springSecurityService of the Spring Security plugin to encode the password before inserting it into the database.
Is there a way to mock that springSecurityService from my unit test in order to get rid of that error?
Failure: Create new individual member(MemberControllerSpec)
| java.lang.NullPointerException: Cannot invoke method encodePassword() on null object
Please find my unit test below.
#TestMixin(HibernateTestMixin)
#TestFor(MemberController)
#Domain([User, IndividualPerson])
class MemberControllerSpec extends Specification {
void "Create new individual member"() {
given:
UserDetailsService userDetailsService = Mock(UserDetailsService)
controller.userDetailsService = userDetailsService
def command = new IndividualPersonCommand()
command.username = 'scott#tiger.org'
command.password = 'What ever'
command.firstname = 'Scott'
command.lastname = 'Tiger'
command.dob = new Date()
command.email = command.username
command.phone = '89348'
command.street = 'A Street'
command.housenumber = '2'
command.postcode = '8888'
command.city = 'A City'
when:
request.method = 'POST'
controller.updateIndividualInstance(command)
then:
view == 'createInstance'
and:
1 * userDetailsService.loadUserByUsername(command.username) >> null
and:
IndividualPerson.count() == 1
and:
User.count() == 1
cleanup:
IndividualPerson.findAll()*.delete()
User.findAll()*.delete()
}
}
One way to mock a service is to use Groovy's MetaClass
import grails.test.mixin.Mock
import grails.plugin.springsecurity.SpringSecurityService
...
#Mock(SpringSecurityService)
class MemberControllerSpec extends Specification {
def setupSpec() {
SpringSecurityService.metaClass.encodePassword = { password -> password }
}
def cleanupSpec() {
SpringSecurityService.metaClass = null
}
....
In this example, the call to SpringSecurityService.encodePassword() will simply return the password in plain text.
An approach using Mocks is discussed here.
You can to use this code to encode password in User:
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password
}
When springSecurityService is null, encodePassword is not called and NPE is not raised
When you use controller unit test with spring security rest plugin in Grails v4/v3, if your controller method reference springSecurityService methods like 'athenticatedUser', there will be NullPointException, because springSecurityService is not autowired into the spring application context.
Add code like below, you can inject springSecurityService and mock it's methods.
class GuessControllerSpec extends Specification implements ControllerUnitTest<GuessController> {
#Override
Closure doWithSpring() {
return {
// mock method
SpringSecurityService.metaClass.getCurrentUser = {return new User()}
// inject into spring context
springSecurityService(SpringSecurityService)
}
}
...
}
Am getting the following error message when testing the controller - see below for code.
How can I correct this?
When I invoke the service method from the controller (run-app) and it works fine.
Exception:
groovy.lang.MissingMethodException: No signature of method:
grails.test.GrailsMock.isOk() is applicable for argument types:
(java.lang.String) values: [H] at ...VControllerSpec.test
something(VControllerSpec.groovy:)
class: VControllerSpec
import grails.test.mixin.TestFor
import spock.lang.Specification
#TestFor(VController)
#Mock(VService)
class VControllerSpec extends Specification {
void "test something"() {
given:
def vServiceMock = mockFor(VService)
vServiceMock.demand.isOk { String yeah -> return true }
controller.vService = vServiceMock.createMock()
when:
def isO = vServiceMock.isOk("H")
then:
isO == true
}
}
class:VService
import grails.transaction.Transactional
#Transactional
class VService {
def isOk = { String yeah ->
def isO = false
return isO
}
}
Thanks,
Steve
Assuming there is an action in VController as:
def myAction() {
vService.isOk('Hello')
}
below test should pass
void 'test service'() {
given:
def vServiceMock = mockFor(FormatService)
vServiceMock.demand.isOk { String yeah -> return true }
controller.vService = vServiceMock.createMock()
when:
def isO = controller.myAction()
then:
isO == true
}
There are few things to optimize here including using a method isOk instead of a closure as best practices.
One is not expected to test a method which is being mocked. When we mock a method, we just assume its implementation is correct and has already been tested (in some other unit test). The purpose of mocking is to limit our focus of testing to limited lines of code (mostly commonly one method), in your case the your controller action. So the above test case could have been written as:
Assuming your action is like this:
def myAction(){
[iso: vServiceMock.isOk()] // assuming isOk returns boolean true
}
void "test myAction"() {
given:
def vServiceMock = mockFor(VService)
vServiceMock.demand.isOk { String yeah -> return true }
controller.vService = vServiceMock.createMock()
when:
def model = controller.myAction()
then:
model.iso //you can skip comparison with true
}
I have a Grails controller that expects an XML payload.
I fetch the XML payload like this in a Grails controller.
def xmlPayload = request.reader.text
That part works fine, but I'm struggling to mock this payload in a unit test.
I've tried both of the following, but the debugger is showing 'request.reader' to be null in both approaches.
Approach #1:
void "test controller method"(){
setup:
def mockBufferedReader = Mock( BufferedReader )
mockBufferedReader.getText() >> '<hello/>'
request.getReader() >> mockBufferedReader
....
Approach #2:
void "test controller method"(){
setup:
def mockBufferedReader = Mock( BufferedReader )
mockBufferedReader.getText() >> '<hello/>'
request.metaClass.getReader = { -> mockBufferedReader }
....
'request' in a unit test is a GrailsMockHttpServletRequest, so I presumed I could mock its methods like this (3rd line of both approaches), but so far no luck.
Thanks for any ideas.
You can do:
class EchoController {
def echo () {
render (request.reader.text)
}
}
#TestFor(EchoController)
class EchoSpec extends Specification {
def "echos XML data" () {
request.XML = '<hello/>'
when:
controller.echo ()
then:
response.text == '<hello/>'
}
}
See Testing XML and JSON Requests in Unit Testing Controllers.
If you only need to provide contents for a request, then you don't need to mock anything.
def "Spock works as expected"() {
given:
def request = new GrailsMockHttpServletRequest(content: '<hello/>')
when:
def result = request.getReader().getText()
then:
result == '<hello/>'
}
One purpose of such Mock classes (as in Spring Test etc.) is to avoid explicit mocking with external libraries.
I am trying to invent some kind of mocking SecureSocial action generators, or SecureSocial itself to be able to unit-test controller methods.
I've found some approaches, like Unit-testing methods secured with Securesocial annotation and Testing a Play2 application with SecureSocial using dependency injection but the thing is, that in that questions authors, in fact, don't do unit testing, but integration testing.
My unit tests look like this:
trait MockDaoProvider extends IDaoProvider {
def entityDao = entityDaoMock
}
val controller = new MyController with MockDaoProvider
"MyController.list" should {
"return an OK" in {
entityDaoMock.list().returns(List())
val result = controller.list()(FakeRequest())
status(result) must equalTo(OK)
}
}
As one can see, I mocked dependencies to isolate and test the behavior that controller method actually does.
Everything was OK until I used SecuredAction from securesocial for MyController.list method. Now I get an exception, and the test fails. I have no idea how I could mock, stub or override SecuredAction and UserAwareAction objects from securesocial. Still I don't want to convert my tests into route(...) tests. They are intended to test only the controller's behavior.
Have someone encountered the same problem? May be there are any hints how it could be solved?
PS: Play framework 2.2.1, securesocial - 2.1.2
It seem like the author of the code really hasn't emphasized testability, which has forced users to come up with their own novel solutions. This one by user jeantil could be helpful:
class FakeAuthenticatorStore(app:Application) extends AuthenticatorStore(app) {
var authenticator:Option[Authenticator] = None
def save(authenticator: Authenticator): Either[Error, Unit] = {
this.authenticator=Some(authenticator)
Right()
}
def find(id: String): Either[Error, Option[Authenticator]] = {
Some(authenticator.filter(_.id == id)).toRight(new Error("no such authenticator"))
}
def delete(id: String): Either[Error, Unit] = {
this.authenticator=None
Right()
}
}
abstract class WithLoggedUser(val user:User,override val app: FakeApplication = FakeApplication()) extends WithApplication(app) with Mockito{
lazy val mockUserService=mock[UserService]
val identity=IdentityUser(Defaults.googleId, user)
import helpers._
import TestUsers._
def cookie=Authenticator.create(identity) match {
case Right(authenticator) => authenticator.toCookie
}
override def around[T: AsResult](t: =>T): execute.Result = super.around {
mockUserService.find(Defaults.googleId) returns Some(identity)
UserService.setService(mockUserService)
t
}
}
val excludedPlugins=List(
,"service.login.MongoUserService"
,"securesocial.core.DefaultAuthenticatorStore"
)
val includedPlugins = List(
"helpers.FakeAuthenticatorStore"
)
def minimalApp = FakeApplication(withGlobal =minimalGlobal, withoutPlugins=excludedPlugins,additionalPlugins = includedPlugins)
which then allows testing like this
"create a new user password " in new WithLoggedUser(socialUser,minimalApp) {
val controller = new TestController
val req: Request[AnyContent] = FakeRequest().
withHeaders((HeaderNames.CONTENT_TYPE, "application/x-www-form-urlencoded")).
withCookies(cookie) // Fake cookie from the WithloggedUser trait
val requestBody = Enumerator("password=foobarkix".getBytes) andThen Enumerator.eof
val result = requestBody |>>> controller.create.apply(req)
val actual: Int= status(result)
actual must be equalTo 201
}
After some thinking, probing and experimenting I've ended up with an elegant solution. The solution relies on "cake pattern" of dependency injection. Like this:
Code in controller:
trait AbstractSecurity {
def Secured(action: SecuredRequest[AnyContent] => Result): Action[AnyContent]
}
trait SecureSocialSecurity extends AbstractSecurity with securesocial.core.SecureSocial {
def Secured(action: SecuredRequest[AnyContent] => Result): Action[AnyContent] = SecuredAction { action }
}
abstract class MyController extends Controller with AbstractSecurity {
def entityDao: IEntityDao
def list = Secured { request =>
Ok(
JsArray(entityDao.list())
)
}
}
object MyController extends MyController with PsqlDaoProvider with SecureSocialSecurity
And test code:
trait MockedSecurity extends AbstractSecurity {
val user = Account(NotAssigned, IdentityId("test", "userpass"), "Test", "User",
"Test user", Some("test#user.com"), AuthenticationMethod("userPassword"))
def Secured(action: SecuredRequest[AnyContent] => play.api.mvc.Result): Action[AnyContent] = Action { request =>
action(new SecuredRequest(user, request))
}
}
val controller = new MyController with MockDaoProvider with MockedSecurity
"IssueController.list" should {
"return an OK" in {
entityDaoMock.list().returns(List())
val result = controller.list()(FakeRequest())
status(result) must equalTo(OK)
}
}
Still there is a drawback - the tests depends on securesocial classes as well... but... is it really a drawback?
I don't know how this approach will work in more complex situations, we'll see.
in the controller there is an action:
def delete = {
withDomain {
it.delete()
flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'chocolateBar.label', default: 'ChocolateBar'), it.name])}"
redirect action: 'list'
}
}
which can be tested in development. while in unit test, the message(..) method throws exception ( groovy.lang.MissingMethodException: No signature of method: longtest.ChocolateBarController.message() is applicable for argument types: (java.util.LinkedHashMap) values: [[code:chocolateBar.label, default:ChocolateBar]]):
public void testDelete() {
controller.params.id = '3'
controller.delete()
assert 'list'==controller.redirectArgs.action
}
After study, a mockTagLib method should be called during setup. But found no correct class name for built-in message(..). Please help.
I've solved the problem in unit controller test. like this:
//This is inside Spock test
#Shared
ResourceBundleMessageSource messageSource = null
#Shared
Closure mockMessage = {Map map ->
return messageSource.getMessage((String)map.code, (Object[])map.args, Locale.default)
}
def setupSpec(){
URL url = new File('grails-app/i18n').toURI().toURL()
messageSource = new ResourceBundleMessageSource()
messageSource.bundleClassLoader = new URLClassLoader(url)
messageSource.basename = 'messages'
messageSource.setDefaultEncoding("utf-8")
}
def setup(){
controller.metaClass.message = mockMessage
}
This code is for spock test, but main idea is also available for normal grails test.
In running phase(not test),
calling "message" in controller class results in calling "message" of ValidationTagLib class,
but they are not bind in unit test phase.
So I made almost same logic of "message" of ValidationTagLib,
and bind it(named "mockMessage") to controller.message.
With this code, you can execute "message" correctly in controller class in test.