I'm trying to unit test a service and I would like to use a mock to override a method on domain object which retrieves a file from a DB.
def mockElem = mockFor(DataElement, false)
mockElem.demand.getFile(){return tempFile}
def dataElem = mockElem.createMock()
dataElem.orderId = "123"
dataElem.id = tempFileName
dataElem.dataType = "cnv"
dataElem.dataStatus = DataStatus.TRANSFERED
mockDomain(DataElement, [dataElem])
When I call a dynamic finder on the data element I want this Mock Domain to be returned with the mockFor demand functionality for getFile. An Assertion error is thrown when the MockDomain line is reached
junit.framework.AssertionFailedError: No call to 'getClass' expected
at this point. Still 1 call(s) to 'getFile' expected. at
groovy.mock.interceptor.StrictExpectation.match(StrictExpectation.groovy:56)
at grails.test.GrailsMock.createMock_closure1(GrailsMock.groovy:136)
at
grails.test.MockUtils.updateMetaClassForClass_closure95(MockUtils.groovy:1297)
at groovy.lang.Closure.call(Closure.java:412) at
groovy.lang.Closure.call(Closure.java:425) at
grails.test.MockUtils.updateMetaClassForClass(MockUtils.groovy:1294)
at grails.test.MockUtils.mockDomain(MockUtils.groovy:470) at
grails.plugin.spock.UnitSpec.mockDomain(UnitSpec.groovy:141) at
com.genospace.inbound.pg.HemeCNVPipelineTestSpec.test processing Heme
file(HemeCNVPipelineTestSpec.groovy:66)
Not sure what is the question.
You got this error because you are mocking an object twice :
with mockFor/createMock
with mockDomain
mockDomain need to know some information about objects being passedas arguments (here it verifies the class is correct) but mockFor did not allow that getClass() was called : you did not add a demand for such call.
Do you really need to mock with demand ?
I think the simple case should work :
def dataElem = new DataElement()
dataElem.orderId = "123"
dataElem.id = tempFileName
dataElem.dataType = "cnv"
dataElem.dataStatus = DataStatus.TRANSFERED
dataElem.file = tempFile
mockDomain(DataElement, [dataElem])
Related
I have a controller that on the save method calls a thread to retrieve some files. The thread has start() in a domain that has this line-
RetrievalThread retrievalThread = grailsApplication.mainContext.getBean ('retrievalThread').
In my unit test I tried this and it worked(I'll keep the other lines omitted that have no bearing right now). Without this line an error occurs saying can't get mainContext on null object,talking about grailsApplication. .
Def mainContext = Mock(ApplicationContext)
MainContext.getBean(_) >>{ name ->
return new MockRetrievalThread()}
The mock thread doesn't do anything.
This test runs fine but, any test after this fail with a null pointer exception with no real information. Looks like a bunch of background grails stuff. Is there a way to clean this up or use something better than what I'm using?
I'm sure there's a way to clean this up in a tearDown, but I think there is a better way.
1.) I would use DI rather than going through grailsApplication.mainContext.getBean; is there a reason you are doing it this way?
class MyController {
def retrievalThread
getFiles() {
return [files: retrievalThread.getFiles(params.id)]
}
}
2.a.) Using DI, you can then just set the controller's retrievalThread to a new instance of MockRetrievalThread within your test.
void "test getFiles"() {
given:
controller.retrievalThread = new MockRetrievalThread()
when:
params.id = 1
def returnedFiles = controller.getFiles()
then:
// assertions
}
2.b.) Or skip the MockRetrievalThread altogether and mock the retrievalThread bean using the mockFor method, and then set the mocked version to the injected instance in your controller.
void "test getFiles"() {
given:
def retrievalThreadMock = mockFor(RetrievalThread)
retrievalThreadMock.demand.getFiles { Integer input ->
return ['file1', 'file2', 'etc.']
}
controller.retrievalThread = retrievalThreadMock.createMock()
when:
params.id = 1
def returnedFiles = controller.getFiles()
then:
// assertions
}
You can use integration testing instead to run the entire app, in order to avoid any beans not being injected.
grails create-integration-test org.bookstore.Book
I never expected that I will need to ask a question on this site because everything is already answered normally but with Scalatra... I haven't find a lot of information so here it is:
I'm not experienced with all that so maybe I'm missing something but from what I understand, if I want to test the API that I develop on Scalatra, I need to start the server everytime I run the test suit, right ?
Second question, how can I reset the invocation counter on a method so I don't have to calculate how many times the method has been called since the beginning of the test suite ? Right now using this give me more than one because it counts the previous test.
there was one(*instance*).*method*(*parameter*)
I can still get around the problem by either counting or putting the test as first test for now but it's not a sustainable solution...
Other thing that I found:
Reset method on the mock... not found
http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html#17
Isolating the test in a class scope:
We need to add
val servlet = new Servlet(eventRepoMock)
addServlet(servlet, "/*")
and we can't repeat the addServlet at every initialization
https://etorreborre.github.io/specs2/guide/SPECS2-3.5/org.specs2.guide.Isolation.html
Last thing that I try is:
servlet.repo = mock[EventRepo]
but repo being a value, I can't change it like this.
Neither of these "solutions" feel very clean so I was wondering if someone had a genius idea that can solve that mess !?
Thank you in advance !
EDIT:
Thanks to Eric's comment the above question are solve(that was easy) but now I have problem because I'm testing the get/post which are asynchronous call so resetting the mock does not happen at the right time... Any suggestion ?
Here's a simplified version of the code:
class EventServiceSpec extends ScalatraSpec with Mockito with Before { def is = s2"""
Event Service
GET an existing event
must return status 200 $get_status200
must return the event with id = :id $get_rightEventElement
must call findById once on the event repository $get_findByIdOnRepo
"""
lazy val anEvent = Event(1, "Some Event"
lazy val eventsBaseUrl = "/events"
lazy val existingEventUrl = s"$eventsBaseUrl/${anEvent.id}"
lazy val eventRepoMock = mock[EventRepository]
lazy val servlet = new Servlet(eventRepoMock)
addServlet(servlet, "/*")
def before = {
eventRepoMock.findById(anEvent.id) returns Option(anEvent)
eventRepoMock.findById(unexistingId) returns None
eventRepoMock.save(anEvent) returns Option(anEvent)
}
def get_status200 = get(existingEventUrl){
status must_== 200
}
def get_findByIdOnRepo = get(existingEventUrl){
// TODO count considering previous test... need to find a cleaner way
there was three(eventRepoMock).findById(anEvent.id)
}
All org.mockito.Mockito functions can still be used in a specs2 specification and reset is one of them.
Now, since you are sharing the state of a mock object across several examples, you not only need to reset the mock state before each example but you also need to make your specification sequential:
class EventServiceSpec extends ScalatraSpec with Mockito
with BeforeAll with BeforeEach {
def is = sequential ^ s2"""
Event Service
GET an existing event
must return status 200 $get_status200
must return the event with id = :id $get_rightEventElement
must call findById once on the event repository $get_findByIdOnRepo
"""
lazy val anEvent = Event(1, "Some Event")
lazy val eventsBaseUrl = "/events"
lazy val existingEventUrl = s"$eventsBaseUrl/${anEvent.id}"
lazy val eventRepoMock = mock[EventRepository]
lazy val servlet = new Servlet(eventRepoMock)
def beforeAll = addServlet(servlet, "/*")
def before = {
reset(eventRepoMock)
eventRepoMock.findById(anEvent.id) returns Option(anEvent)
eventRepoMock.findById(unexistingId) returns None
eventRepoMock.save(anEvent) returns Option(anEvent)
}
def get_status200 = get(existingEventUrl){
status must_== 200
}
def get_findByIdOnRepo = get(existingEventUrl){
there was one(eventRepoMock).findById(anEvent.id)
}
}
I've got a service, which is using sendJMSMessage method which is provided by jms-grails plugin.
I want to write some unit tests, but I'm not sure how to "mock" this method, so it just does nothing at all.
Any tips?
You can metaClass the method to have it return whatever you want.
#Test
void pluginCode() {
def myService = new MyService()
def wasCalled = false
myService.metaClass.sendJMSMessage = {String message ->
//I like to have an assert in here to test what's being passed in so I can ensure wiring is correct
wasCalled = true
null //this is what the method will now return
}
def results = myService.myServiceMethodThatCallsPlugin()
assert wasCalled
}
I like to have a wasCalled flag when I'm returning null from a metaClassed method because I don't particularly like asserting that the response is null because it doesn't really assure that you're wired up correctly. If you're returning something kind of unique though you can do without the wasCalled flag.
In the above example I used 1 String parameter but you can metaClass out any number/type of parameters to match what actually happens.
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.
I have a method in a custom taglib like so:
def deleteAction = {attrs ->
def id = attrs['id']
def type = attrs['type']
def clazz = attrs['class']
def html = new MarkupBuilder(out)
html.span(class: "${clazz} ui-icon ui-icon-trash {id:'${id}'}")
}
I have a controller that uses this method and I'm trying to stub it out for a unit test, so I have the following:
def mockMyTagLib = mockFor(MyTagLib)
mockMyTagLib.demand.deleteAction(1) {id, type, clazz ->
def html = new MarkupBuilder(new StringWriter())
html.span(class: "${clazz} ui-icon ui-icon-trash {id:'${id}'}")
}
controller.metaClass.mn = mockMyTagLib.createMock()
But I keep getting the following:
No more calls to 'deleteAction'
expected at this point. End of
demands.
Am I doing something wrong here? Here is it's actual usage in the controller:
"${mn.deleteAction(id: it.id, type: 'bookProduct', 'class': 'del-book-product')}"
The following is from Testing - Reference Documentation
... You then specify the name of the
method that you want to mock with an
optional range as its argument. This
range determines how many times you
expect the method to be called, so if
the number of invocations falls
outside of that range (either too few
or too many) then an assertion error
will be thrown. If no range is
specified, a default of "1..1" is
assumed, i.e. that the method must be
called exactly once.
You've specified demand.deleteAction(1) which means that the method must be called once and only once.
Also, if you want, you can always set your mock to be loose by specifying it as the second parameter in mockFor (defaults to strict)
mockFor(class, loose = false)