How to mock a request when unit testing a service in grails - unit-testing

I am trying to unit test a service that has a method requiring a request object.
import org.springframework.web.context.request.RequestContextHolder as RCH
class AddressService {
def update (account, params) {
try {
def request = RCH.requestAttributes.request
// retrieve some info from the request object such as the IP ...
// Implement update logic
} catch (all) {
/* do something with the exception */
}
}
}
How do you mock the request object ?
And by the way, I am using Spock to unit test my classes.
Thank you

This code seems to work for a basic unit test (modified from Robert Fletcher's post here):
void createRequestContextHolder() {
MockHttpServletRequest request = new MockHttpServletRequest()
request.characterEncoding = 'UTF-8'
GrailsWebRequest webRequest = new GrailsWebRequest(request, new MockHttpServletResponse(), ServletContextHolder.servletContext)
request.setAttribute(GrailsApplicationAttributes.WEB_REQUEST, webRequest)
RequestContextHolder.setRequestAttributes(webRequest)
}
It can be added as a function to your standard Grails unit test since the function name does not start with "test"... or you can work the code in some other way.

One simple way to mock these, is to modify the meta class for RequestContextHolder to return a mock when getRequestAttributes() is called.
I wrote up a simple spec for doing this, and was quite surprised when it didn't work! So this turned out to be a quite interesting problem. After some investigation, I found that in this particular case, there are a couple of pitfalls to be aware of.
When you retrieve the request object, RCH.requestAttributes.request, you are doing so via an interface RequestAttributes that does not implement the getRequest() method. This is perfectly fine in groovy if the returned object actually has this property, but won't work when mocking the RequestAttributes interface in spock. So you'll need to mock an interface or a class that actually has this method.
My first attempt at solving 1., was to change the mock type to ServletRequestAttributes, which does have a getRequest() method. However, this method is final. When stubbing a mock with values for a final method, the stubbed values are simply ignored. In this case, null was returned.
Both these problems was easily overcome by creating a custom interface for this test, called MockRequestAttributes, and use this interface for the Mock in the spec.
This resulted in the following code:
import org.springframework.web.context.request.RequestContextHolder
// modified for testing
class AddressService {
def localAddress
def contentType
def update() {
def request = RequestContextHolder.requestAttributes.request
localAddress = request.localAddr
contentType = request.contentType
}
}
import org.springframework.web.context.request.RequestAttributes
import javax.servlet.http.HttpServletRequest
interface MockRequestAttributes extends RequestAttributes {
HttpServletRequest getRequest()
}
import org.springframework.web.context.request.RequestContextHolder
import spock.lang.Specification
import javax.servlet.http.HttpServletRequest
class MockRequestSpec extends Specification {
def "let's mock a request"() {
setup:
def requestAttributesMock = Mock(MockRequestAttributes)
def requestMock = Mock(HttpServletRequest)
RequestContextHolder.metaClass.'static'.getRequestAttributes = {->
requestAttributesMock
}
when:
def service = new AddressService()
def result = service.update()
then:
1 * requestAttributesMock.getRequest() >> requestMock
1 * requestMock.localAddr >> '127.0.0.1'
1 * requestMock.contentType >> 'text/plain'
service.localAddress == '127.0.0.1'
service.contentType == 'text/plain'
cleanup:
RequestContextHolder.metaClass = null
}
}

Related

How do I create a detached mock with closure in Spock framework?

It's good to see we can have detached mocks since Spock 1.1, especially if we want to create a mock utilities for boilerplate UT coding.
But it seems DetachedMockFactory refuses to be initialised with closure.
class MockUtils {
def mockFactory = new DetachedMockFactory()
IRequestContextGetter mockDefaultRequestContext() {
return mockFactory.Mock(IRequestContextGetter) {
tenant >> Constants.TENANT
currencyType >> CurrencyEnum.SGD
}
}
}
It throws the following error:
'Mock' in 'spock.mock.DetachedMockFactory' cannot be applied to
'(java.lang.Class<com.bytedance.cg.gcrm.salesplanning.common.utils.requestcontext.IRequestContextGetter>,
groovy.lang.Closure)'
How do I create a detached mock with closure?
Short answer, you can't.
Longer answer, in Spock mock interactions are actually part of the specification, not the mock object itself. So, detached mocks can't have any interactions until they get attached to a specification.
There is a way, but it is quite cumbersome. You can define you own IDefaultResponse to handle the mock interactions and customize your response that way.
import org.spockframework.util.*
import org.spockframework.mock.*
import spock.mock.*
interface Foo {
String getBar()
}
class CustomResponse implements IDefaultResponse {
public Object respond(IMockInvocation invocation) {
'foo'
}
}
def factory = new DetachedMockFactory()
def stub = factory.Stub(Foo, defaultResponse: new CustomResponse())
assert stub.bar == 'foo'
Try it in the Groovy Web Console

Unit test fails

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.

Grails/Spock Unit Testing

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.

Testing a Grails controller using a mock service with GMock (0.8.0)

I have a grails 2.1 app which has a controller that calls a method on a service, passing in a request and a response:
class FooController {
def myService
def anAction() {
response.setContentType('text/xml')
myservice.service(request,response)
}
I want to unit test this method. And I want to do so using GMock (version 0.8.0), so this is what I tried:
def testAnAction() {
controller.myService = mock() {
service(request,response).returns(true)
}
play {
assertTrue controller.anAction()
}
}
Now this fails saying that that it failed expectations for request.
Missing property expectation for 'request' on 'Mock for MyService'
However, if I write my test like this:
def testAnAction() {
def mockService = mock()
mockService.service(request,response).returns(true)
controller.myService = mockService
play {
assertTrue controller.anAction()
}
}
The test will pass fine. As far as I am aware they are both valid uses of the GMock syntax, so why does the first one fail and the second one not?
Cheers,
I assume you write your tests in a test class FooControllerTest generated by grails.
In a such way, FooControllerTest class is annoted by #TestFor(FooController) wich inject some usefull attributes.
So request is an attribute of your test class, not a variable in the local scope.
It's why it is not reachable from a internal Closure.
I'm convinced that following code could work (I have not tested yet) :
def testAnAction() {
def currentRequest = request
def currentResponse = response
controller.myService = mock() {
service(currentRequest,currentResponse).returns(true)
}
play {
assertTrue controller.anAction()
}
}

Mock a method for Grails Unit Test

In one of my unit tests, I am having some difficulty getting a mocked method to executed. I have the following test code:
void testExample() {
def mockICFService = new MockFor(ICFService)
...
//Mock the methods
controller.metaClass.icfList = { def person ->
println "icfList"
return [new IC(conceptId:'12345')]
}
mockICFService.demand.getAllIC(1..1) { def id, def withHist, def appId ->
println "mocking service"
return new Person()
}
...
def model = controller.detail()
}
Inside of detail in my controller class I create a Person via the ICFService's getAllIC(). This part works correctly. Later in the function, however, there is a call to icfList (which is defined in the controller). Through println's I have determined that the call is still being made, although it is returning an empty array. I believe that this is because the array is populated based on data in the servletContext, but in Unit Testing there is no access to that (hence my trying to mock it out).
Does anyone know how to force the test to use the mocked version of controller.icfList instead of calling the actual method in controller?
When I try your code, what blows up for me is the mocked service, and the part that works properly is the mocked-out icfList() method. The opposite of your observation, interestingly. For what it's worth, here's what I did:
First replace new MockFor() class instantiation with the mockFor() method. Then you need to inject the mock service into the controller.
def mockICFService = mockFor(ICFService)
controller.iCFService = mockICFService.createMock()
By doing the above, only the mocked versions of icfList() and getAllIC() get called, so you are not using the servletContext at all. Check out the Grails testing documentation for more info.