Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > Mocking a method with a block

Reply
Thread Tools

Mocking a method with a block

 
 
Fernando Guillen
Guest
Posts: n/a
 
      07-31-2010
Hi people,

I have an application that download emails from an email account an
process them.

What I would like to do is to mock the mail download petition and not
process the real mails but an array of mails I have for this propos.

This is the precise situation: I have this:

Code:
Net::POP3.start( opts[:server], port, opts[:user], opts[:pass] )
do |pop|
pop.each_mail do |m|
block.call( m )
end
end
I would like to have a mock that if on my test call to

=> Net:OP3.start( opts[:server], port, opts[:user], opts[ass] ) do
|pop|

Not any mailing petition is done but the body of the method is still
working but not with real mails but with an array of fake mails like
this:

=> mails = [ File.read('/dir/mail1.raw_mail'),
File.read('/dir/mail2.raw_mail')]

Is this possible?.. am I completely lost?.. is there any better way to
do this?

Any suggestion is welcome.

Thanks

f.
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
 
 
 
Jesús Gabriel y Galán
Guest
Posts: n/a
 
      07-31-2010
On Sat, Jul 31, 2010 at 5:38 PM, Fernando Guillen
<> wrote:
> Hi people,
>
> I have an application that download emails from an email account an
> process them.
>
> What I would like to do is to mock the mail download petition and not
> process the real mails but an array of mails I have for this propos.
>
> This is the precise situation: I have this:
>
>
Code:
> =A0 =A0 =A0Net::POP3.start( opts[:server], port, opts[:user], opts[:pass]=
Code:
)
> do |pop|
> =A0 =A0 =A0 =A0pop.each_mail do |m|
> =A0 =A0 =A0 =A0 =A0block.call( m )
> =A0 =A0 =A0 =A0end
> =A0 =A0 =A0end
> 

>
> I would like to have a mock that if on my test call to
>
> =3D> Net:OP3.start( opts[:server], port, opts[:user], opts[ass] ) do
> |pop|
>
> Not any mailing petition is done but the body of the method is still
> working but not with real mails but with an array of fake mails like
> this:
>
> =3D> mails =3D [ File.read('/dir/mail1.raw_mail'),
> File.read('/dir/mail2.raw_mail')]
>
> Is this possible?.. am I completely lost?.. is there any better way to
> do this?


One option would be to change the start method of Net:OP3 to do
exactly what you describe.
Remember that Ruby is open and you can redefine any method on any
class. You can always alias the method start before changing it, so
you can revert to the original definition after the test.

Jesus.

 
Reply With Quote
 
 
 
 
Fernando Guillen
Guest
Posts: n/a
 
      08-01-2010
Jesús Gabriel y Galán wrote:
> One option would be to change the start method of Net:OP3 to do
> exactly what you describe


This is a good idea.

I can open the class Net:OP3 and redefine the method .start.

Now, how can I organize this to charge the redefinition of the method at
the beginning of the test and revert it at the end of the test.

I mean, if I have a file that redefine the Net:OP3.start method I can
'require' it but I don't know how to 'un-require' it.

Also I would like to simulate that the Net:OP3.start charges my mails
array (different on every test) and if I redefine this method in a
generic way I don't know how to use my mails array inside the redefined
method.

Example, if I have my own redefinition of the method like:

Code:
class Net::POP3
def self.start( *, &block )

(How can I put my mails array in here?)

end
end
I think I need some kind of mocking tutorial or something, if you know
any one and you can offer me the link it will be great.

Thanks

f.
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Jesús Gabriel y Galán
Guest
Posts: n/a
 
      08-02-2010
On Sun, Aug 1, 2010 at 10:40 AM, Fernando Guillen
<> wrote:
> Jes=FAs Gabriel y Gal=E1n wrote:
>> One option would be to change the start method of Net:OP3 to do
>> exactly what you describe

>
> This is a good idea.
>
> I can open the class Net:OP3 and redefine the method .start.
>
> Now, how can I organize this to charge the redefinition of the method at
> the beginning of the test and revert it at the end of the test.


You can use alias_method to "save" a copy of the original method with
another name. You can do this in the setup of your tests, and revert
back to that version in the teardown:

irb(main):001:0> class A
irb(main):002:1> def self.test
irb(main):003:2> "original test"
irb(main):004:2> end
irb(main):005:1> end

irb(main):013:0> class A
irb(main):014:1> class << self
irb(main):015:2> alias_method rig_test, :test
irb(main):016:2> end
irb(main):017:1> end

# at this point you can call A.test and A.orig_test and both do the same
# now you can create a new version of the method:

irb(main):020:0> class A
irb(main):021:1> def self.test
irb(main):022:2> "new test, original was [#{orig_test}]"
irb(main):023:2> end
irb(main):024:1> end
=3D> nil
irb(main):025:0> A.test
=3D> "new test, original was [original test]"
irb(main):026:0> A.orig_test
=3D> "original test"

As you can see, you can still refer to the original method. To put it back:

irb(main):027:0> class A
irb(main):028:1> class << self
irb(main):029:2> alias_method :test, rig_test
irb(main):030:2> end
irb(main):031:1> end
=3D> #<Class:A>
irb(main):032:0> A.test
=3D> "original test"

(this leaves the orig_test method around, but I don't think that would
be a problem).

>
> I mean, if I have a file that redefine the Net:OP3.start method I can
> 'require' it but I don't know how to 'un-require' it.
>
> Also I would like to simulate that the Net:OP3.start charges my mails
> array (different on every test) and if I redefine this method in a
> generic way I don't know how to use my mails array inside the redefined
> method.


If this is just for running a test you could use a global variable or
a constant:

irb(main):034:0> MY_EMAILS =3D %w{a b c d e}
=3D> ["a", "b", "c", "d", "e"]
irb(main):035:0> class A
irb(main):036:1> class << self
irb(main):037:2> alias_method rig_test, :test
irb(main):038:2> def test
irb(main):039:3> "my emails: #{MY_EMAILS}"
irb(main):040:3> end
irb(main):041:2> end
irb(main):042:1> end
=3D> nil
irb(main):043:0> A.test
=3D> "my emails: abcde"

You can also look at class_eval and define_method to build a closure
around your emails' variable (sorry, don't have time now to do that,
I'll come back later if you need it).

Jesus.

 
Reply With Quote
 
Fernando Guillen
Guest
Posts: n/a
 
      08-02-2010
Jesús, Thank you so much for your help.

I think I have enough information to work by my self.

Thanks for your work.

f.
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Brian Candler
Guest
Posts: n/a
 
      08-02-2010
Fernando Guillen wrote:
> Is this possible?.. am I completely lost?.. is there any better way to
> do this?


I'd suggest you use an existing mocking library like "mocha". Example:

-------- 8< ----------------
require 'net/pop'
require 'test/unit'
require 'rubygems'
require 'mocha'

# code to test
class Foo
attr_reader pts
def initialize(opts)
@opts = opts
end
def bar(block)
Net:OP3.start( opts[:server], opts[ort], opts[:user],
opts[ass] ) do |pop|
pop.each_mail do |m|
block.call( m )
end
end
end
end

# tests
class MyTest < Test::Unit::TestCase
def test_1
mails = ["xxxxx","yyy"]
mockpop = mock
mockpop.expects(:each_mail).multiple_yields(*mails )
Net:OP3.expects(:start).with("127.0.0.1", 110, "a",
"b").yields(mockpop)

foo = Foo.new(:server=>"127.0.0.1", ort=>110, :user=>"a",
ass=>"b")
res = []
foo.bar(lambda { |x| res << x.size })
assert_equal [5,3], res
end

# more expectation-based style
def test_2
mails = ["xxxxx","yyy"]
mockpop = mock
mockpop.expects(:each_mail).multiple_yields(*mails )
Net:OP3.expects(:start).with("127.0.0.1", 110, "a",
"b").yields(mockpop)

mockblock = mock
seq = sequence(:block)
mockblock.expects(:call).with(mails[0]).in_sequence(seq)
mockblock.expects(:call).with(mails[1]).in_sequence(seq)

foo = Foo.new(:server=>"127.0.0.1", ort=>110, :user=>"a",
ass=>"b")
foo.bar(mockblock)
end
end
-------- 8< ----------------

I find there's something unsatisfying about tests which look like this.
Sometimes you can spend more effort on the mechanics of mocking than on
solving the original problem, and the resulting tests are closely
coupled to the internal implementation of your function. But that *is*
what you asked for

Refactoring might make your code easier to test. e.g.

-------- 8< ----------------
require 'net/pop'
require 'test/unit'
require 'rubygems'
require 'mocha'

class Foo
attr_reader pts
def initialize(opts)
@opts = opts
end
def bar(block)
for_all_messages do |m|
block.call(m)
end
end
private
def for_all_messages(&blk)
Net:OP3.start( opts[:server], opts[ort], opts[:user],
opts[ass] ) do |pop|
pop.each_mail(&blk)
end
end
end

class MyTest < Test::Unit::TestCase
def test_1
foo = Foo.new({})

mails = ["xxxxx","yyy"]
foo.expects(:for_all_messages).multiple_yields(*ma ils)
res = []
foo.bar(lambda { |x| res << x.size })
assert_equal [5,3], res
end
end
-------- 8< ----------------

HTH,

Brian.
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Fernando Guillen
Guest
Posts: n/a
 
      08-02-2010
Brian Candler wrote:
> Fernando Guillen wrote:
>> Is this possible?.. am I completely lost?.. is there any better way to
>> do this?

>
> I'd suggest you use an existing mocking library like "mocha".



This was how I started to try mock the Net:OP3.

But instead of using the .yields method, that I didn't know about, I
tried to mock directly the Net:OP3.start method and this didn't work.

Thanks a lot for your comprehensive examples and also for the
re-factoring suggestion for an easier mock.

Best regards.

f.
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Brian Candler
Guest
Posts: n/a
 
      08-02-2010
Fernando Guillen wrote:
> But instead of using the .yields method, that I didn't know about, I
> tried to mock directly the Net:OP3.start method and this didn't work.


It would be really useful if you could attach arbitrary behaviour to a
mocked method - perhaps 'returns' with a block:

m = [1,2,3]
Net:OP3.expects(:start).returns { m.each { puts m }; m.size }

This is something I've missed badly in the past, and I've ended up
mocking directly using Object.new and defining singleton methods.
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Fernando Guillen
Guest
Posts: n/a
 
      08-02-2010
Hi again people,

Following your suggestions I am trying to build an small library to
abstract this behavior on a reusable way:

* http://github.com/fguillen/NetPopMock

My problem now is that I'm not able to use the _raw_mails_ array sent on
the **NetPopMock.fake** call

See the test example:

*
http://github.com/fguillen/NetPopMoc...ck_test.rb#L15

Into the mocked class:

* http://github.com/fguillen/NetPopMoc...op_mock.rb#L30

Jesus was the first one suggesting me to use _class_eval_ and I told him
that I thought I was enough information to work on my self.. well that
was not true.. I need help again

I know the solution is in a mix of _eval_ and _binding_ but I don't find
it.

Thanks again

f.
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Fernando Guillen
Guest
Posts: n/a
 
      08-02-2010
Thinking I found the solution:

*
http://github.com/fguillen/NetPopMoc...ece171a7b371dd

Do you think is a clean solution?

Regards

f.
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
method def in method vs method def in block Kyung won Cheon Ruby 0 11-21-2008 08:48 AM
Mocking Objects with Typemock Isolator Mocking framework for .NetUnit Testing mo.sparrow ASP .Net 0 08-22-2008 11:55 AM
Fo:Block can you check to see if a block contains any text by using the block id? morrell XML 1 10-10-2006 07:18 PM
Mocking Bird Babies Don Dunlap Digital Photography 5 05-14-2006 03:17 PM



Advertisments
 



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57