Can someone explain why this spec is failing?
RSpec results:
User#presenter memoizes presenter
Failure/Error: user.presenter
(<UserPresenter (class)>).new(...)
expected: 1 time with any arguments
received: 2 times with arguments: (...)
From my understanding of the conditional assignment operator it should only assign #presenter once.
# app/models/user.rb
def User
def presenter
#presenter ||= UserPresenter.new(self)
end
end
# app/presenters/user_presenter.rb
class UserPresenter
end
# spec/models/user_spec.rb
describe User do
let(:user){ build_stubbed(:user) }
describe "#presenter" do
it "memoizes presenter" do
UserPresenter.should_receive(:new).once
user.presenter
user.presenter # should not call .new
end
end
end
The issue is this line:
UserPresenter.should_receive(:new).once
This chunk of code simply sets up the expectation that a UserPresenter will receive the new method one time. It tears down your original UserPresenter#new method and replaces it with a method that returns nil. Because nil is falsy, the #presenter instance variable is not memoized.
To fix this, you can specify a return value in your expectation:
UserPresenter.should_receive(:new).once.and_return "some truthy value"
or equivalently
UserPresenter.should_receive(:new).once { "some truthy value" }
or if you absolutely want to call the original method
UserPresenter.should_receive(:new).once.and_call_original
or with the new expect syntax
expect(UserPresenter).to receive(:new).once.and_call_original
See this for more information about expecting a message, and this for more information about calling the original method. This has some further discussion about RSpec's should_receive method.
Related
I have the following class:
class State
def self.alive
:alive
end
def self.dead
:dead
end
end
Testing is simple:
it 'should report the expected state for alive' do
State.alive.must_equal :alive
end
it 'should report the expected state for dead' do
State.dead.must_equal :dead
end
I want to add a class method which returns a random state.
class State
# ...
def self.random
[alive, dead].sample
end
However, I'm unsure of which assertion I need to use.
Currently, I am testing it as follows:
it 'should return a random state' do
%i[dead alive].must_include State.random
end
Which is back to front. The test above is testing the literal array rather that the State class.
Is there a better way to test that a method returns a value included within a specified array?
Before I offer you a solution, I want to unravel some details about the Spec DSL and Minitest. The must_include methods is the Spec DSL expectation for the assert_includes assertion. That method has the following signature:
assert_includes collection, obj, msg = nil
When the expectation is created, the signature is:
Collection#must_include obj, msg = nil
So what you are really asking is for a way of calling these methods with the argument order reversed. This is pretty simple to do by creating new methods that use your desired argument order. First, we must create the assertion:
module Minitest::Assertions
##
# Fails unless +obj+ is included in +collection+.
def assert_included_in obj, collection, msg = nil
msg = message(msg) {
"Expected #{mu_pp(obj)} to be included in #{mu_pp(collection)}"
}
assert_respond_to collection, :include?
assert collection.include?(obj), msg
end
end
Now that we have the assertion method, we can create the Spec DSL expectation:
module Minitest::Expectations
##
# See Minitest::Assertions#assert_included_in
#
# collection.must_be_one_of obj
#
# :method: must_be_one_of
infect_an_assertion :assert_included_in, :must_be_one_of, :reverse
end
Now that we have defined the expectation, and the assertion it uses, we can use it in the test:
it "should return a random state" do
State.random.must_be_one_of %i[dead alive]
end
I would take it one step further, and use the value monad to call the expectation:
it "should return a random state" do
value(State.random).must_be_one_of %i[dead alive]
end
The prompt:
Extend the Array class to include a method named my_each that takes a block, calls the block on every element of the array, and then returns the original array.
class Array
def my_each(&prc)
if block_given?
proc.call(self)
else
for i in (0..self.length-1)
puts self[i]
end
end
self
end
end
This is what I put together and I don't have a good understanding of how Blocks/Procs work within this context, but somehow I magically wrote the code that passed 3 of the 4 RSPEC tests.
describe "#my_each" do
it "calls the block passed to it" do
expect do |block|
["test array"].my_each(&block)
end.to yield_control.once
end
it "yields each element to the block" do
expect do |block|
["el1", "el2"].my_each(&block)
end.to yield_successive_args("el1", "el2")
end
it "does NOT call the built-in #each method" do
original_array = ["original array"]
expect(original_array).not_to receive(:each)
original_array.my_each {}
end
it "is chainable and returns the original array" do
original_array = ["original array"]
expect(original_array.my_each {}).to eq(original_array)
end
end
All of the above RSPEC tests passes with the exception of the second one, where my code returns [["el1", "el2"]] when ["el1", "el2"] is expected. Can someone please give me an explanation of how or why I am receiving a nested array here?
Can someone also give me an explanation of how the code is running as a block is passing through this method? I'm not sure if my "else" condition is actually even necessary in the context of the RSPEC tests. I'm generally confused by the concept of passing blocks through self-written methods and how they interact with the method itself.
Thanks in advance!
In the first part of your condition, you pass the whole array to the block:
if block_given?
proc.call(self)
else
# ...
E.g. for an array of ["el1", "el2"] you do proc.call(["el1", "el2"]). What you expect in the test are two consecutive calls:
proc.call("el1")
proc.call("el2")
To do that you need to use a loop also in the first part of the condition and pass there an array element, not the whole array:
if block_given?
for i in (0..self.length-1)
proc.call(self[i])
end
else
for i in (0..self.length-1)
puts self[i]
end
end
proc.call(self)
is the culprit. self is the whole array.
Extend the Array class to include a method named my_each
class Array
def my_each
end
end
that takes a block,
#every method in ruby accepts a block, it is just ignored when not used (yielded to). Do nothing.
calls the block on every element of the array,
class Array
def my_each
for element in self
yield element
end
end
end
and then returns the original array.
# "for" loops do this. Do nothing. It should pass.
I am a ruby newbie, I have managed to pull out the code for ruby but writing rspecs for them seems problematic. It's hard to understand the way to write rspecs even after reading few tutorials. Someone please help me to write for an input method then I would try to refactor it for the rest.
RB file:
module RubyOperations
class Operations
def input(num)
RubyOperations.log('Enter a number:[Between 1 to 10]',:BOTH)
num = Integer(gets.chomp)
raise StandardError if num <= 0 || num > 10
return num
rescue StandardError, ArgumentError => e
RubyOperations.log(e,:ERROR)
end
end
end
RSPEC:
describe 'RubyOperations' do
describe 'Operations' do
describe '.input' do
context 'when number is provided' do
it 'returns the number provided' do
expect(RubyOperations.input(num)).to eq(Integer)
end
end
end
end
end
You can check the class of the output of the method to equal integer
require 'ruby_final_operations'
describe 'RubyOperations' do
describe 'Operations' do
describe '.input' do
context 'when number is provided' do
it 'returns the number provided' do
expect(RubyOperations.input(num).class).to eq(Integer)
(or)
expect(RubyOperations.input(num)).to be_a_kind_of(Integer)
end
end
end
end
end
And whenever you write rspec keep in mind
If the method for which you are writing rspec deals with manipulations in your db then check if db is manipulated or not
Or if you are writing rspec for any methods which returns an object then procced like this
if a method is defined like
def square_of_a_number(num)
num*num
end
Then write rspec like this
it 'returns square of a number' do
expect(square_of_a_number(2).to eq(4)
end
For any methods that you know the output of a method will be that then hardcode the input or user Faker gem for input of the method expect the expected result of that method
There are few issues with code that you have shared:
1) In the Operations class, the method input receives an argument which is not used anywhere because of this line: num = Integer(gets.chomp). Basically gets is the method that waits for user input, and the assignment num = ... overrides the value of argument (num) that is passed into the method, hence it is pointless to pass num argument into the method.
2) In your spec sample you call input method on RubyOperations module, while the input lives in class Operations under namespace RubyOperations. Also method input is not a class method but instance method. So proper method call would be: RubyOperations::Operations.new.input(5)
3) To run a spec for input method you would need to stub user input. RSpec-mocks gem can help you with that - https://github.com/rspec/rspec-mocks. It has allow stub method: allow(object).to receive(:gets) { "5" }
The whole sample will be:
it 'returns the number provided' do
# instantiate object that we will test
subject = RubyOperations::Operations.new
# we stub method 'gets' and whenever it is called we return string "5"
allow(subject).to receive(:gets) { "5" }
# we call method input with argument 1, the argument 1 is pointless as described in point 1) and can be omitted
expect(subject.input(1)).to eq(5)
end
I am trying to make a simplistic implementation of AOP in ruby. I was able to implement before and after advices, I got stuck with around advice.
This is the target class that is going to be advised:
class MyClass
def method
puts "running method"
end
end
This is the Aspect class to instantiate objects capable of making advices:
class Aspect
def advise(class_name, method, type, &block)
class_name.send(:alias_method, :proceed, :method)
class_name.send(:define_method, :method) do
case type
when :before
yield
proceed
when :after
proceed
yield
when :around
yield(proceed) # * proceed is the old version of the method
end
end
end
end
(*) Yield should execute the block around MyClass#proceed on the current object when method is invoked.
Creating the target and the aspect:
mc = MyClass.new
a = Aspect.new()
Invoking the method without advising it:
puts mc.method
Advising MyClass#method with around:
a.advise(MyClass, :method, :around) do |proceed|
puts "First"
proceed # this is not working *
puts "Last"
end
puts mc.method
(*) I am not being able to pass something to identify the call of proceed, that is the invocation of the old method without the advice.
The output should be:
First
running method
Last
In Ruby, a method call looks like this:
receiver.method(arguments)
Or, you can leave off the receiver if the receiver is self.
So, to call a method named proceed on some receiver, you would write
receiver.proceed
However, in your implementation, you don't keep track of what the receiver should be, so since you don't know the receiver, you simply cannot call the method.
Note that there are lots of other problems with your approach as well. For example, if you advise multiple methods, you will alias them all to the same method, overwriting each other.
I believe there are two things going wrong here.
This section of code
when :around
yield(proceed) # * proceed is the old version of the method
end
Calls the block given to advise providing the output of the proceed method as an argument.
So your output probably looks something like:
running method
First
Last
This block
a.advise(MyClass, :method, :around) do |proceed|
puts "First"
proceed # this is not working *
puts "Last"
end
Just evaluates the argument given as proceed. If a method is given it does not call it. So taking problem 1 into consideration in your case the original definition of method (aliased to proceed) returns nil (output of return) which will be passed as the value to the proceed argument in the block when yielded. the block ends up evaluating to something like
puts "First"
nil
puts "Last"
mc.method is called.
To address the second part, you may want to consider using send. Because the inner workings of your aspect may not be known to your code that calls it. It may change over time, so what ever calls Aspect.advise shouldn't make assumptions that the original method will still be accessible. Instead, it should take an argument (the new method name) and send it to the object. Making the block passed to advise:
a.advise(MyClass, :method, :around) do |aliased_method_name|
puts "First"
send(aliased_method_name)
puts "Last"
end
And adjusting the around item added to your class when advise is called to the following:
when :around
yield(:proceed) # * proceed is the old version of the method
end
If you do both of these things, your around section will calls the provided block, using the symbol for the new alias for the overridden method.
N.B.: This approach won't work for methods that require any arguments.
This is what I did. In the definition of Aspect#advise now I use a Proc, like this:
when :around
yield Proc.new { proceed }
end
And when calling the method to advise MyClass#method with :around parameter I use this:
a.advise(MyClass, :method, :around) do |original|
puts "First"
original.call
puts "Last"
end
I got:
First
running method
Last
Here's the fixed version that will work for arguments, and avoid clobbering.
class Aspect
##count = 0
def self.advise(class_name, method, type=nil, &block)
old_method = :"__aspect_#{method}_#{##count += 1}"
class_name.send(:alias_method, old_method, method)
class_name.send(:define_method, method) do |*args, &callblock|
case type
when :before
yield
send(old_method, *args, &callblock)
when :after
send(old_method, *args, &callblock)
yield
when :around, nil
yield lambda {
send(old_method, *args, &callblock)
}
end
end
end
end
class Foo
def foo(what)
puts "Hello, #{what}!"
end
end
Aspect.advise(Foo, :foo) do |y|
puts "before around"
y.yield
puts "after around"
end
Aspect.advise(Foo, :foo, :before) do
puts "before"
end
Aspect.advise(Foo, :foo, :after) do
puts "after"
end
Foo.new.foo("world")
# before
# before around
# Hello, world!
# after around
# after
class MyClass
def test
...
end
end
tmp = MyClass.new
tmp.test do |t|
"here"
end
Why am I getting the error
multiple values for a block parameter (0 for 1)
Here is a slightly longer example, based on your code:
class MyClass
def test
yield self
end
def my_own_puts s
puts s
end
end
tmp = MyClass.new
tmp.test do |t|
t.my_own_puts "here"
end
Running this code will output "here".
What is happening is there is a method test that can take a block of code, so you can call it with the do .. end syntax. Because it is passing an arg to yield, that arg is available to the block, so you give this to the block using the do |some_arg_name| ... end syntax.
The yield is where the block gets executed in the test method, and in this case I to yield I pass in self. Since the block now has access to self (an instance of MyClass), the block can call the my_own_puts method on it, and print out "here".
if test is defined with a yield statement, when that statement is reached and if there is a parameter on the yield statement, that parameter will be put into the block variable t. Thus if you have:
def test
.....
yield x
......
end
then x will be the value of t when yield is executed.
With your help, I was able to get the code working like this
class MyClass
def test
a = yield self
puts a
end
end
tmp = MyClass.new
tmp.test do |t|
"here"
end
Thanks, I had to tweak your code a bit but it works the way I wanted to now.
Passing a block to a function (as Bob shows in his answer) is overkill in this case. If you are reading in a string and printing it out, all you should need is something like:
class MyClass
def test(a)
puts a
end
end
tmp = MyClass.new
tmp.test("here")
Using a block might function correctly, but you are calling a lot of unnecessary code and making the true nature of your code unclear.
Proper block usage aside, let me address the particular error message you are seeing. When you say tmp.test do |t|, Ruby is expecting tmp.test to yield a single value which it will temporarily call t and pass to the block (think like the block is a function and you are passing it the argument your yield statement as a parameter). In your case, the method test method must not be yield-ing anything, thus the message "(0 for 1)" implies that it is seeing zero objects yielded when it is expecting to see one. I don't know what your code for test does, but check and make sure that test yields exactly one value.