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 > 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. 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.