Is it possible to encapsulate a test method to reuse it?
I could describe what I want to achieve as
// generic test method
def genericMethod(args){
given:
def request = args.request
when:
def response = http.post(request)
then:
response.data == args.responseData
}
def specificTest1(){
genericMethod([request: 1, responseData: 2])
}
def specificTest2(){
genericMethod([request: 'A', responseData: 'B'])
}
Unfortunately, Spock doesn't seem to work this way.
I am aware of expect: and where: labels but they are not suitable for my case.
Is it possible to simulate what I designated in the code above?
Partial Success
I could partially succeed when using expect: only.
// generic test method
def genericMethod(args){
def request = args.request
def response = http.post(request)
response.data == args.responseData
}
def specificTest1(){
expect:
genericMethod([request: 1, responseData: 2])
}
The test run but specificTest1() tests only whether genericMethod is T of F. (Yes, it's obvious. :-) )
As this was also raised as an issue #724, I'll post the answer here as well:
You need to explicitly add assert before your comparison. Implicit assertions are only applied in special block such as the then: block.
So response.data == args.responseData would get it to work.
Related
class ExternalObject
attr_accessor :external_object_attribute
def update_external_attribute(options = {})
self.external_object_attribute = [1,nil].sample
end
end
class A
attr_reader :my_attr, :external_obj
def initialize(external_obj)
#external_obj = external_obj
end
def main_method(options = {})
case options[:key]
when :my_key
self.my_private_method(:my_key) do
external_obj.update_external_attribute(reevaluate: true)
end
else
nil
end
end
private
def my_private_method(key)
old_value = key
external_object.external_object_attribute = nil
yield
external_object.external_object_attribute = old_value if external_object.external_object_attribute.nil?
end
end
I want to test following for main_method when options[:key] == :my_key:
my_private_method is called once with argument :my_key and it has a block {external_obj.update_external_attribute(reevaluate: true) } , which calls update_external_attribute on external_obj with argument reevaluate: true once.
I'm able to test my_private_method call with :my_key argument once.
expect(subject).to receive(:my_private_method).with(:my_key).once
But how do I test the remaining part of the expectation?
Thank you
It could be easier to answer your question if you post your test as well.
The setup, the execution and asseriotns/expectations.
You can find a short answer in this older question.
You can find useful to read about yield matchers.
I would suggest to mock the ExternalObject if you already haven't. But I can't tell unless you post your actual test code.
I'm going to answer your question. But, then I'm going to explain why you should not do it that way, and show you a better way.
In your test setup, you need to allow the double to yield so that the code will fall through to your block.
RSpec.describe A do
subject(:a) { described_class.new(external_obj) }
let(:external_obj) { instance_double(ExternalObject) }
describe '#main_method' do
subject(:main_method) { a.main_method(options) }
let(:options) { { key: :my_key } }
before do
allow(a).to receive(:my_private_method).and_yield
allow(external_obj).to receive(:update_external_attribute)
main_method
end
it 'does something useful' do
expect(a)
.to have_received(:my_private_method)
.with(:my_key)
.once
expect(external_obj)
.to have_received(:update_external_attribute)
.with(reevaluate: true)
.once
end
end
end
That works. The test passes. RSpec is a powerful tool. And, it will let you get away with that. But, that doesn't mean you should. Testing a private method is ALWAYS a bad idea.
Tests should only test the public interface of a class. Otherwise, you'll lock yourself into the current implementation causing the test to fail when you refactor the internal workings of the class - even if you have not changed the externally visible behavior of the object.
Here's a better approach:
RSpec.describe A do
subject(:a) { described_class.new(external_obj) }
let(:external_obj) { instance_double(ExternalObject) }
describe '#main_method' do
subject(:main_method) { a.main_method(options) }
let(:options) { { key: :my_key } }
before do
allow(external_obj).to receive(:update_external_attribute)
allow(external_obj).to receive(:external_object_attribute=)
allow(external_obj).to receive(:external_object_attribute)
main_method
end
it 'updates external attribute' do
expect(external_obj)
.to have_received(:update_external_attribute)
.with(reevaluate: true)
.once
end
end
end
Note that the expectation about the private method is gone. Now, the test is only relying on the public interface of class A and class ExternalObject.
Hope that helps.
I have a custom RSpec matcher that checks that a job is scheduled. It's used like so:
expect { subject }.to schedule_job(TestJob)
schedule_job.rb:
class ScheduleJob
include RSpec::Mocks::ExampleMethods
def initialize(job_class)
#job_class = job_class
end
...
def matches?(proc)
job = double
expect(job_class).to receive(:new).and_return job
expect(Delayed::Job).to receive(:enqueue).with(job)
proc.call
true
end
This works fine for positive matching. But it does not work for negative matching. e.g:
expect { subject }.not_to schedule_job(TestJob) #does not work
For the above to work, the matches? method needs to return false when the expectations are not met. The problem is that even if it returns false, the expectations have been created regardless and so the test fails incorrectly.
Any ideas on how to make something like this work?
I had to look for it, but I think it's nicely described here in the rspec documentation
Format (from the docs) for separate logic when using expect.not_to:
RSpec::Matchers.define :contain do |*expected|
match do |actual|
expected.all? { |e| actual.include?(e) }
end
match_when_negated do |actual|
expected.none? { |e| actual.include?(e) }
end
end
RSpec.describe [1, 2, 3] do
it { is_expected.to contain(1, 2) }
it { is_expected.not_to contain(4, 5, 6) }
# deliberate failures
it { is_expected.to contain(1, 4) }
it { is_expected.not_to contain(1, 4) }
end
i am trying to use cookie in grails 3.
i tried this plugin but i don't know why its not work at all..
cookieService.setCookie('username', customer?.email)
and i use this code for call it from gsp
<g:cookie name="username"/>
i also tried this way..
def cokusername = cookieService.setCookie('username', customer?.email)
println "cookieService.getCookie('username') = "+cookieService.getCookie('username')
redirect(controller: "toko",cokusername: cokusername)
and this is in my tokoController.groovy index :
def index={
def toko = CifLogo.executeQuery("from CifLogo order by rand()",[max: 10])
// def itemRandom = Item.executeQuery("from Item where cif = :cif order by rand()",[max:12,cif:cif])
def awdf = cookieService.getCookie('username')
println "awdf = "+awdf
println "cokusername = "+params.cokusername
[tokoList:toko,cokusername:awdf]
}
i have no idea to retrieve my cookie. :(
update
def index(){
def toko = CifLogo.executeQuery("from CifLogo order by rand()",[max: 10])
// def itemRandom = Item.executeQuery("from Item where cif = :cif order by rand()",[max:12,cif:cif])
def awdf = cookieService.getCookie('username')
println "awdf = "+awdf
println "cokusername = "+params.cokusername
[tokoList:toko,cokusername:awdf]
}
i tried to print cookie like this..
def awdf = request.getCookie('username')
println "awdf = "+awdf
println "cokusername = "+params.cokusername
request.cookies.each { println "${it.name} == ${it.value}" }
and this is what the result
From what I can see this line:
redirect(controller: "toko",cokusername: cokusername)
Should be:
redirect(controller: "toko",params:[cokusername: cokusername])
Also actions using closures in grails 3 will have undesired results. You should change to methods. Hence this line:
def index={
SHould be:
def index(){
Apart from this it seems the cookieService code should work fine, so I can only assume its being caused my the closure index that should be a method.
Another thing could be the fact that you are doing a redirect, which will clear the request and not persist any cookies that were set before the redirect
I don't know why, but maybe it's a bug.
i use this code to setCookie
cookieService.setCookie(name:"username", value: customer?.email, maxAge: 24*60*60, path: "/")
after read this code.
and i cannot deleteCookie with this code.
cookieService.deleteCookie(cookieService.findCookie("username"))
because when i print cookieService.findCookie("username") it returns javax.servlet.http.Cookie#78cbf320
and method deleteCookie(Cookie cookie) from this link
so i think it mustbe deleted.
but still availlable.
so i can answer this question about setCookie not deleteCookie
i also tried this way to delete cookie..but still failed.
CookieService.setCookie(name:"username", value: "", maxAge: 0, path: "/")
I need to perform a null or empty check on a collection; I think that !members?.empty is incorrect. Is there a groovier way to write the following?
if (members && !members.empty) {
// Some Work
}
There is indeed a Groovier Way.
if (members) {
//Some work
}
does everything if members is a collection. Null check as well as empty check (Empty collections are coerced to false). Hail Groovy Truth. :)
FYI this kind of code works (you can find it ugly, it is your right :) ) :
def list = null
list.each { println it }
soSomething()
In other words, this code has null/empty checks both useless:
if (members && !members.empty) {
members.each { doAnotherThing it }
}
def doAnotherThing(def member) {
// Some work
}
!members.find()
I think now the best way to solve this issue is code above. It works since Groovy 1.8.1 http://docs.groovy-lang.org/docs/next/html/groovy-jdk/java/util/Collection.html#find(). Examples:
def lst1 = []
assert !lst1.find()
def lst2 = [null]
assert !lst2.find()
def lst3 = [null,2,null]
assert lst3.find()
def lst4 = [null,null,null]
assert !lst4.find()
def lst5 = [null, 0, 0.0, false, '', [], 42, 43]
assert lst5.find() == 42
def lst6 = null;
assert !lst6.find()
My Grails code has a search function that redirects to another controller action after performing a findAllBy query:
def results = Foo.findAllByBar(baz)
redirect(action: "result", params: [results: results])
findAllByBar returns an ArrayList with models, as expected, but after the redirect the receiving action gets a String array. Worse, when there is only one result it doesn't even get an array, it just gets a String.
Given that I have to iterate through the results in the receiving view, doing it on a string will meticulously print every letter individually. We can all agree that that's probably not the ideal behaviour.
A redirect results in a new GET request with the parameters in the querystring, e.g. /controller/result?foo=bar&baz=123 - you can't put objects there since it's just a string.
You could put the ids of the objects in the params and load them in the result action:
def action1 = {
def results = Foo.findAllByBar(baz)
redirect(action: "result", params: [resultIds: results.id.join(',')])
}
def result = {
def resultIds = params.resultIds.split(',')*.toLong()
def results = Foo.getAll(resultIds)
}
or put them in Flash scope:
def action1 = {
flash.results = Foo.findAllByBar(baz)
redirect(action: "result")
}
def result = {
def results = flash.results
}
It sounds like you want to use the chain method instead of the redirect method. Chain lets you pass a model as a parameter similar to render.
An example would be:
chain(action:'result',model:[results:results])
Heres a link for further information:
http://www.grails.org/doc/latest/ref/Controllers/chain.html