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.
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
}
}
I have an integration test where I sometimes want to mock the return of a service method. However, I have seen that once I mock that method, the subsequent tests that call it will also use the mocked function.
Is this normal? If so, how can I have test which sometimes use mocked functions and sometimes use the real implementation?
Here is my code:
MyController {
def someService
def save(){
...
def val = someService.methodToMock()//sometimes want to mock other times, not
...
}
}
MyTest {
def "test 1"(){
...
//I want to mock here
myController.someService.metaClass.methodToMock = { [] }
...
myController.save()
}
def "test 2"(){
...
//I don't want to mock here, however
// it is returning the mocked results
myController.save()
}
}
In general you don't want to change anything to do with metaclasses in integration or functional tests, only in unit tests. It's expected that you'll be doing this in unit tests and there's automatic support for restoring the original metaclass after each test or after each test class runs depending on the version of Grails and how things are configured. But this isn't the case in integration tests.
There are several different approaches you can use. If you use untyped dependency injection, e.g. def someService, then you can overwrite the real service instance with anything you want, and as long as it has the method(s) that you'll be invoking during the test method the controller won't know or care that it's not the real service.
I like to use a map of closures in this case, since Groovy will invoke a closure as if it were a method. So for 'test 1' you could do this:
def "test 1"() {
...
def mockedService = [methodToMock: { args -> return ... }]
myController.someService = mockedService
...
myController.save()
}
This works because you get a new instance of the controller for each test, and you change the service just for that instance, but the real service isn't affected at all.
Your controller invokes someService.methodToMock(), which is actually someService.get('methodToMock').call(), but the map access and closure invocation syntax can take advantage of Groovy's syntactic sugar to look like a regular method call.
Another option is to subclass the service and override the method(s) that you want, and replace the injected instance with that. This or something like it would be necessary if you type the dependency injection (e.g. SomeService someService). Either create a named subclass (class TestSomeService extends SomeService { ... }) or create an anonymous inner class:
def "test 1"() {
...
def mockedService = new SomeService() {
def methodToMock(args) {
return ...
}
}
myController.someService = mockedService
...
myController.save()
}
Altering the metaClass in one test will absolutely affect other tests. You're altering the groovy system, and need to perform some special cleanup if you're metaClassing. At the end my methods where I metaClass, I call a function to revoke the metaClass changes, passing in the name of the class that was metaClassed, and the instance metaClassed if there was one.
def "some authenticated method test"() {
given:
def user = new UserDomain(blah blah blah)
controller.metaClass.getAuthenticatedUser = { return user }
when:
controller.authenticatedMethod() // which references the authenticated user
then:
// validate the results
cleanup:
revokeMetaClassChanges(theControllerClass, controller)
}
private def revokeMetaClassChanges(def type, def instance = null) {
GroovySystem.metaClassRegistry.removeMetaClass(type)
if (instance != null) {
instance.metaClass = null
}
}
Alternatively, you can just mock the service in the test. A method similar to that mentioned by Burt could be:
def "some test"() {
given:
def mockSomeService = mockFor(SomeService)
mockSomeService.demand.methodToMock(1) { def args ->
return []
}
controller.someService = mockSomeService.createMock()
when:
controller.save()
then:
// implement your validations/assertions
}
So, I am trying to test the following Grails service implementation:
class BookService {
def searchBooks(String search) {
if (!search) {
return []
}
Book.searchBooks(search)
}
}
It uses a method defined in the Book domain class:
class Book {
String title
static def searchBooks(String search) {
Book.findByTitleLike(search)
}
}
So, this is the test code I've been trying:
#TestFor(BookService)
#Mock(Book)
class BookServiceSpec extends Specification {
void "should search books"() {
setup:
new Book(title: 'The Stand').save()
new Book(title: 'Under the Dome').save()
new Book(title: 'Bag of Bones').save()
when:
service.searchBooks('ones')
then:
1 * Book.searchBooks()
}
}
But, it always fails with:
Too few invocations for:
1 * Book.searchBooks() (0 invocations)
I've debugged the code running in an IDE and the method seems to be indeed executed, but Spock does not register the execution.
What am I missing?
Is there something else I have to do to mock the domain class method?
Here is a sample project with the code.
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.
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.