Ok, here is the class, I want to Unit Test, its part of a large app
and is based on EventMachine library.
I want to mock the class TickServer ( i.e not stub it) . Since in
actual scenario, you can''t do this on this class:
@server = TickServer.new # will toss an exception at your face
you must initialize the server like this:
EventMachine.run {
EventMachine.start_server("192.168.2.252",6200,TickServer) do |conn|
conn.hello_world
end
}
Where conn holds the instance of the class TickServer. So, I want to
mock this class, so that i don''t have to create an actual socket
connection.
Please look in the code, carefully. most of the methods in class are
actually callbacks, which respond to certain events.
Is it possible to unit test this with Mocha?
require "eventmachine"
class TickServer < EventMachine::Connection
attr_accessor :chunked_data,:client_status
def valid_protocol? data
return false unless data =~
/(\d{3})([^<>]*)<([^><]*)>##(.*?)##/
return true
end
# method is a callback, gets called whenever data is there in socket to read
def receive_data data
data.chomp!
while data && data.length > 0
data_array = data.split(/\r?\n/m,2)
@chunked_data << data_array[0]
data = data_array[1]
# two level of protocol handling
if @chunked_data =~ /(\d{3})(.*)<(.*)?>(##(.*)##)/
if valid_protocol?(@chunked_data)
dispatch_request
else
send_error_without_callback "Invalid Protocol code"
end
@chunked_data = ""
end
end # end of while loop
end
def send_error_without_callback msg
send_data msg +"\n"
end
def dispatch_request
send_data "#{Time.now.to_s} #{@chunked_data} \n"
end
# this method gets automatically called, when a client connects
# so this place for initializing stuff
def post_init
@chunked_data = ""
@client_status = true
end
# method gets called when a client disconnects
def unbind
@client_status = false
end
def hello_world
p "Hello World"
end
end
--
gnufied
-----------
There was only one Road; that it was like a great river: its springs
were at every doorstep, and every path was its tributary.
Looks like there''s a couple options.
You could mock the start_server call:
EventMachine.expects(:start_server).with("192.168.2.252",
6200,TickServer).yields(conn=mock)
conn.expects(:hello_world)
Or, you should be able to mock any instance of TickServer:
TickServer.any_instance.expects(:hello_world)
I didn''t read anything in the code below that made me think this
wouldn''t work.
On Dec 15, 2006, at 6:17 PM, hemant wrote:
> Ok, here is the class, I want to Unit Test, its part of a large app
> and is based on EventMachine library.
>
> I want to mock the class TickServer ( i.e not stub it) . Since in
> actual scenario, you can''t do this on this class:
>
> @server = TickServer.new # will toss an exception at your face
>
> you must initialize the server like this:
>
> EventMachine.run {
> EventMachine.start_server("192.168.2.252",6200,TickServer) do
|conn|
> conn.hello_world
> end
> }
>
> Where conn holds the instance of the class TickServer. So, I want to
> mock this class, so that i don''t have to create an actual socket
> connection.
>
> Please look in the code, carefully. most of the methods in class are
> actually callbacks, which respond to certain events.
>
> Is it possible to unit test this with Mocha?
>
>
>
> require "eventmachine"
>
> class TickServer < EventMachine::Connection
> attr_accessor :chunked_data,:client_status
>
> def valid_protocol? data
> return false unless data =~
/(\d{3})([^<>]*)<([^><]*)>##(.*?)##/
> return true
> end
>
> # method is a callback, gets called whenever data is there in
> socket to read
> def receive_data data
> data.chomp!
> while data && data.length > 0
> data_array = data.split(/\r?\n/m,2)
> @chunked_data << data_array[0]
> data = data_array[1]
> # two level of protocol handling
> if @chunked_data =~ /(\d{3})(.*)<(.*)?>(##(.*)##)/
> if valid_protocol?(@chunked_data)
> dispatch_request
> else
> send_error_without_callback "Invalid Protocol code"
> end
> @chunked_data = ""
> end
> end # end of while loop
> end
>
> def send_error_without_callback msg
> send_data msg +"\n"
> end
>
> def dispatch_request
> send_data "#{Time.now.to_s} #{@chunked_data} \n"
> end
>
> # this method gets automatically called, when a client connects
> # so this place for initializing stuff
> def post_init
> @chunked_data = ""
> @client_status = true
> end
>
> # method gets called when a client disconnects
> def unbind
> @client_status = false
> end
>
> def hello_world
> p "Hello World"
> end
>
> end
>
>
>
> --
> gnufied
> -----------
> There was only one Road; that it was like a great river: its springs
> were at every doorstep, and every path was its tributary.
> _______________________________________________
> mocha-developer mailing list
> mocha-developer at rubyforge.org
> http://rubyforge.org/mailman/listinfo/mocha-developer
On 12/16/06, Jay <jay at jayfields.com> wrote:> Looks like there''s a couple options. > > You could mock the start_server call: > EventMachine.expects(:start_server).with("192.168.2.252", > 6200,TickServer).yields(conn=mock) > conn.expects(:hello_world) > > Or, you should be able to mock any instance of TickServer: > TickServer.any_instance.expects(:hello_world) > > I didn''t read anything in the code below that made me think this > wouldn''t work. >Cool... I did this: $:.unshift File.join(File.dirname(__FILE__), "mocha", "lib") require "mocha" require "tick_server" require "test/unit" class TickServerTest < Test::Unit::TestCase def setup setup_client end def setup_client EventMachine::Connection.stubs(:initialize).returns(true) @server = TickServer.new "foo" end def test_new_request request = "310 <IBM>##lol##" @server.receive_data request @server.expects(:send_error_without_callback).never end end I know, I did wrong, and i got the error: Loaded suite tick_server_test Started terminate called after throwing an instance of ''std::runtime_error'' what(): not initialized Aborted I will try your approach and see. -- gnufied ----------- There was only one Road; that it was like a great river: its springs were at every doorstep, and every path was its tributary.
Ok, I sort of got it working.
class TickServerTest < Test::Unit::TestCase
def setup
setup_client
end
def setup_client
EventMachine::Connection.any_instance.stubs(:receive_data).with(:data).returns(true)
EventMachine::Connection.any_instance.stubs(:send_data).with(:data).returns(true)
@server = TickServer.new gid
end
def gid
Guid.new.hexdigest.to_s
end
def test_new_request
request = "640 <ibm>##lol##"
@server.expects(:send_error_without_callback).never
# only one of the expected method gets called here
# if you try to expect both, then you will get a failure
# in your code
#@server.expects(:dispatch_request).at_least_once
@server.expects(:send_to_client).at_least_once
@server.receive_data request
end
end
And here is code, I am trying to test:
require "eventmachine"
class TickServer < EventMachine::Connection
attr_accessor :chunked_data,:client_status
def valid_protocol? data
return false unless data =~
/(\d{3})([^<>]*)<([^><]*)>##(.*?)##/
return true
end
# method is a callback, gets called whenever data is there in socket to read
def receive_data data
data.chomp!
while data && data.length > 0
data_array = data.split(/\r?\n/m,2)
@chunked_data << data_array[0]
data = data_array[1]
# two level of protocol handling
if valid_protocol?(@chunked_data)
dispatch_request
else
send_error_without_callback "Invalid Protocol code"
end
@chunked_data.replace("")
end # end of while loop
end
def send_error_without_callback msg
send_data msg +"\n"
end
def dispatch_request
p "###Calling dispatch_request"
send_to_client "#{Time.now.to_s} #{@chunked_data} \n"
end
def send_to_client msg
p "###Calling send_to_client"
send_data msg
end
end
# above test case passes happily, without any problems, but what I
really wonder about the testcase is, the output:
Loaded suite tick_server_test
Started
"###Calling dispatch_request"
.
Finished in 0.002732 seconds.
1 tests, 2 assertions, 0 failures, 0 errors
If you look the two lines of test case:
# @server.expects(:dispatch_request).at_least_once
@server.expects(:send_to_client).at_least_once
It would mean, that method send_to_client must be called once, and
since test is passing, I would assume that indeed its getting called.
But then, why "###Calling send_to_client" is not getting printed?
Also, when I comment out test case line for dispatch_request, I get error:
#<Mocha::Mock:0x-242efa62>.send_to_client - expected calls: at least
1, actual calls: 0
How can this be true? since dispatch_request makes an immediate call
to send_to_client, whenever dispatch_request gets called
send_to_client also should be called.
I am surely missing something or have gone blind. Will anybody tell me
the reason of above behaviour.
I think you have misunderstood what Mocha is doing for you. When you use expects() or stubs() on a real object, Mocha temporarily replaces the named method with a new implementation which can verify the method was called in the way you expected and/or return the specified result. The original implementation is not invoked. So when you have... @server.expects(:send_to_client).at_least_once send_to_client is indeed being called - try commenting out the call to send_to_client in dispatch_request and you will see a Mocha expectation error - but the original implementation of send_to_client is not invoked. And when you have... @server.expects(:dispatch_request).at_least_once @server.expects(:send_to_client).at_least_once send_to_client is never called, because the original implementation of dispatch_request is not invoked. -- James. http://blog.floehopper.org
On 12/16/06, James Mead <jamesmead44 at gmail.com> wrote:> I think you have misunderstood what Mocha is doing for you. When you > use expects() or stubs() on a real object, Mocha temporarily replaces > the named method with a new implementation which can verify the method > was called in the way you expected and/or return the specified result. > The original implementation is not invoked. > > So when you have... > > @server.expects(:send_to_client).at_least_once > > send_to_client is indeed being called - try commenting out the call to > send_to_client in dispatch_request and you will see a Mocha > expectation error - but the original implementation of send_to_client > is not invoked. > > And when you have... > > @server.expects(:dispatch_request).at_least_once > @server.expects(:send_to_client).at_least_once > > send_to_client is never called, because the original implementation of > dispatch_request is not invoked. >Yup...thanks James for explaining it for me. I get it now. -- gnufied ----------- There was only one Road; that it was like a great river: its springs were at every doorstep, and every path was its tributary.