I am doing Grails tutorials on IBM(here) but I am a quite disappointed by an integration test.
To sum up : I call a method who render a JSON object according to the ID (iata).
My domain is :
class Airport {
String name
String iata
}
My controller is :
class AirportController {
// In order to enable scaffolding
def scaffold = Airport
def iata = {
def iata = params.id?.toUpperCase() ?: "NO IATA"
def airport = Airport.findByIata(iata)
if (!airport) {
airport = new Airport(iata: iata, name: "Not found")
}
render airport as JSON
}
}
When I do :
http://localhost:8080/trip-planner/airport/iata/foo (in order to retreive null value) or
http://localhost:8080/trip-planner/airport/iata/DEN (for DENVER), the method works fine !
The issue is my Integration tests :
class AirportControllerTests extends GroovyTestCase {
void testWithGoodIata(){
def controller = new AirportController()
controller.metaClass.getParams = { ->
return ["id":"den"]
}
controller.iata()
def response = controller.response.contentAsString
assertTrue response.contains("Denver")
}
void testWithWrongIata() {
def controller = new AirportController()
controller.metaClass.getParams = { ->
return ["id":"foo"]
}
controller.iata()
def response = controller.response.contentAsString
assertTrue response.contains("\"name\":\"Not found\"")
}
}
The problem is:
Whenever I run the tests (by running : grails test-app -integration trip.planner.AirportControllerTests), I will always obtain a good behavior in the first test and a groovy.lang.MissingMethodException in the second test. (even if I switch the two : the second test always fail)
If I run them separately , it works.
The exception occurred at this line (in the controller) : def airport = Airport.findByIata(iata)
Is that someting to do with "transactional" ? Any help would be great :)
P.S : I am using Grails 2.2.1
The exception stacktrace :
groovy.lang.MissingMethodException: No signature of method: trip.planner.Airport.methodMissing() is applicable for argument types: () values: []
at trip.planner.AirportController$_closure4.doCall(AirportController.groovy:39)
at trip.planner.AirportControllerTests.testWithWrongIata(AirportControllerTests.groovy:25)
I suspect the metaclass changes you're making in one test are somehow leaking through to the other. But you don't need to (and shouldn't) manipulate the metaclass in an integration test, just say
def controller = new AirportController()
controller.params.id = "den"
You only need to do mocking for unit tests.
Bear in mind that the tutorial you're looking at was written way back in 2008 (in the Grails 1.0.x days), and Grails has moved on a very long way since then, with some components (including testing) having been through one or more complete rewrites.
Related
I recently upgraded a project from 2.2 to 2.4.4, and upgraded the integration tests by replacing
IntegrationTest extends GroovyTestCase
with
#TestMixin(IntegrationTestMixin)
My controllers have save methods e.g. :
class IssueController {
def save() {
...
if (!issueService.save(issue)) {
render(view: "create", model: [issueInstance: issue])
return
}
}
and the integration test (in test/integration) :
#Before
void setUp() {
ic = new IssueController()
}
#Test
void testValidSave() {
ic.params.issueNo = "test"
ic.save()
assert ic.flash.successAlert == "Saved issue test"
assert ic.response.redirectUrl == '/issue/list'
}
but my Integrations tests, when calling ic.save() don't call the controller save method (and so fail). If I rename the save() method to say saveIt(), and the call to ic.saveIt(), everything works fine
but I don't want to have to rename all my controller method names.
There is no need to remove allowedMethods or no need to change to GET just do in test case
ic.request.method = "POST" if allowed method is post.
Within a Spock unit test, I am trying to test the behaviour of a method findRepositoriesByUsername independent of getGithubUrlForPath, both belonging to the same service.
Repeated attempts to use the metaClass have failed:
String.metaClass.blarg produces an error No such property: blarg for class: java.lang.String
service.metaClass.getGithubUrlForPath to modify the service instance doesn't work
GithubService.metaClass.getGithubUrlForPath to modify the service class doesn't work
Tried adding/modifying methods on the metaClass in the test methods' setup and when blocks, neither worked as expected
The test:
package grails.woot
import grails.test.mixin.TestFor
#TestFor(GithubService)
class GithubServiceSpec extends spock.lang.Specification {
def 'metaClass test'() {
when:
String.metaClass.blarg = { ->
'brainf***'
}
then:
'some string'.blarg == 'brainf***'
}
def 'can find repositories for the given username'() {
given:
def username = 'username'
def requestPathParts
when: 'the service is called to retrieve JSON'
service.metaClass.getGithubUrlForPath = { pathParts ->
requestPathParts = pathParts
}
service.findRepositoriesByUsername(username)
then: 'the correct path parts are used'
requestPathParts == ['users', username, 'repos']
}
}
The service:
package grails.woot
import grails.converters.JSON
class GithubService {
def apiHost = 'https://api.github.com/'
def findRepositoriesByUsername(username) {
try{
JSON.parse(getGithubUrlForPath('users', username, 'repos').text)
} catch (FileNotFoundException ex) {
// user not found
}
}
def getGithubUrlForPath(String ... pathParts) {
"${apiHost}${pathParts.join('/')}".toURL()
}
}
I've tested the String.metaClass.blarg example in the groovy shell (launched by grails), and it did as expected.
Do I have a fundamental misunderstanding here? What am I doing wrong? Is there a better way to handle the desired test (replacing a method on the service under test)?
This is how the tests can be written to make them pass:
def 'metaClass test'() {
given:
String.metaClass.blarg = { -> 'brainf***' }
expect:
// note blarg is a method on String metaClass
// not a field, invoke the method
'some string'.blarg() == 'brainf***'
}
def 'can find repositories for the given username'() {
given:
def username = 'username'
def requestPathParts
when: 'the service is called to retrieve JSON'
service.metaClass.getGithubUrlForPath = { String... pathParts ->
requestPathParts = pathParts
[text: 'blah'] // mimicing URL class
}
service.findRepositoriesByUsername(username)
then: 'the correct path parts are used'
requestPathParts == ['users', username, 'repos']
}
Why don't you use Spock's great Mocking abilities?
Look at http://spockframework.github.io/spock/docs/1.0/interaction_based_testing.html#_creating_mock_objects
There is no need to peek inside metaclass itself, you can create some stub object, and demanded method will be called instead of original one. Also you can use Groovy's MockFor and StubFor, they can be a little bit easier.
You cannot fully trust metaclass inside spock tests specification.
There is some complex logic inside it, which can easyly mess thing's up. Try run some tests under debugger, and you will see it.
Spock uses metaclasses under the hood. It can override your own try's.
I am trying to create an integration test and I am running in a problem in the binding of an association.
I am trying to test a save method in a controller.
I have the domain class Event
class Event {
...
..
.
Organizer organizer
}
In the controller save method I have
def save() {
...
..
.
def passedOrganizerId = params.organizer.id // (1)
//Some comprobations
// if comprobations pass
def event = new Event(params) // (2)
.
}
The method seems to work fine but I want to create an integration test.
class EventControllerTests extends GroovyTestCase {
void testSave() {
def params = [:]
// Params setup (3)
controller.params.putAll(params)
controller.save()
...
..
.
}
}
I have tried several ways to do the params setup but all fail.
If in the test method line (3) I enter: params.organizer.id = 3 it fails because organizer is null
If I enter params['organizer.id'] = 3 then it fails in line (1)
If I enter
params.organizer = [:]
params.organizer.id = 3
It does not fail but the databinding is not working. That it is to say event.organizer is null after line (2)
How to make the data binding work in the test method?
You can set up params in test as follow:
...
controller.params.organizer = Organizer.get(3)
controller.save()
...
i.e. use domain objects as controller parameters directly
I stumbled across this when doing a refactoring with Grails 2.0.1 but I pulled the basics of this problem out into a straight groovy 1.8.6 test and it still failed. I came across it because my method used to take no parameters and I changed it to take 1 parameter. When I changed the implementing production code none of my tests failed. Which is weird because the metaClassing that I had in the test was setup to accept no parameters but it was still responding to my production code when I passed in a parameter. So in the example below I'm wondering why the second metaClassing is being invoked and not the first. It doesn't accept any parameters and as you can see I'm passing one in. If you switch the order of the metaClassing then it works appropriately but order shouldn't matter in this case since the method signature is different. Any insight into why this is happening would be greatly appreciated.
import groovy.util.GroovyTestCase
class FirstTest extends GroovyTestCase {
void testStuff() {
def object = new Object()
object.metaClass.someMethodName = {Object obj ->
"ONE"
}
object.metaClass.someMethodName = {
"TWO"
}
def result = object.someMethodName(new Object())
assert "ONE" == result //result is equal to "TWO" in this case
}
}
EDIT
Seems as if my above code may be more confusing than helpful so here is the actual code.
Original Production Code:
def create() {
render(view: "create", model: [domains: Domain.myCustomListMethod().sort{it.cn}])
}
Original Test Code:
#Test
void createShouldIncludeAListOfAllDomainsInModel() {
def directory = GldapoDirectory.newInstance(
"", [
url: "http://url.com",
userDn: "someUserName",
password: "superSecretPassword"
])
controller.session.userDirectory = directory
Domain.metaClass.'static'.myCustomListMethod = {
[[cn:"1"], [cn:"2"]]
}
controller.create()
assert [[cn:"1"], [cn:"2"]] == controller.modelAndView.model.domains
}
I then updated the production code to pass in the session.userDirectory and my test still passed unmodified even though it's not setup to receive any parameters:
def create() {
render(view: "create", model: [domains: Domain.list(session.userDirectory).sort{it.cn}])
}
Closures by default take one parameter (of class Object), even if none is declared (accessible via the default variable it)
So your second closure is overriding the first
I'm using Grails 1.3.7. I'm trying to test a redirect in my integration test. Here is my controller and method in question ...
class HomeController {
def design = {
....
if (params.page) {
redirect(uri: "/#/design/${params.page}")
}
else {
redirect(uri: "/#/design")
}
break;
}
}
However in my integration test, the call to "controller.response.redirectedUrl" is failing (always returns null) even though I know the redirect call is being made (verified through logging). What is wrong with the integration test below?
class HomeControllerTests extends grails.test.ControllerUnitTestCase {
....
void testHomePageDesign() {
def controller = new HomeController()
// Call action without any parameters
controller.design()
assert controller.response.redirectedUrl != null
assertTrue( responseStr != "" )
}
Thanks, - Dave
Changing your HomeControllerTests to extend GrailsUnitTestCase should fix the problem.
class HomeControllerTests extends grails.test.GrailsUnitTestCase {
....
}
The various ways of generating a test class all seem to vary the class that is extended.
create-integration-test => GroovyTestCase
create-unit-test => GrailsUnitTestCase
create-controller => ControllerUnitTestCase
However, according to the Test section of the Grails User Guide, GrailsUnitTestCase is the core part of the testing frame and, at least in 1.3.7, that is the best class to base test classes on.