Siêu thị PDFTải ngay đi em, trời tối mất

Thư viện tri thức trực tuyến

Kho tài liệu với 50,000+ tài liệu học thuật

© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

Rails for Java Developers phần 8 ppsx
MIỄN PHÍ
Số trang
35
Kích thước
248.6 KB
Định dạng
PDF
Lượt xem
1453

Rails for Java Developers phần 8 ppsx

Nội dung xem thử

Mô tả chi tiết

TESTING INTERACTIONS WITH MOCK OBJECTS 227

Like Java, Ruby does not ship with a mock object library. We will use

FlexMock.9 You can install FlexMock via RubyGems:

gem install flexmock

To make FlexMock available to all tests in a Rails application, require it

in test_helper.rb:

Download code/rails_xt/test/test_helper.rb

require 'flexmock'

Now for a test. The Rails XT sample application does not have a man￾ager layer, so we will introduce a new feature in the controller layer.

Instead of simply accessing all quips, users should be allowed to filter

quips based on their preferences. Our application will store user pref￾erences in the session and use a third-party API to filter content. The

third-party API will be implemented through a @filter_service instance on

the controller.

It is possible to call the FlexMock API via freestanding classes. It is

much simpler, however, to just begin our test case by including Flex￾Mock::TestCase:

Download code/rails_xt/test/functional/quips_controller_test.rb

include FlexMock::TestCase

Adding FlexMock::TestCase gives us helper methods for creating mocks,

and it automatically validates the mocks during teardown.

The QuipsController should provide a new method, list_with_user_filter. This

method should return all quips, minus any that are rejected by the

FilterService. Here is the test:

Download code/rails_xt/test/functional/quips_controller_test.rb

def test_list_with_user_filter

filter = flexmock("filter")

filter.should_expect do |m|

m.filter(Array,nil).returns([quips(:quip_1)])

end

@controller.instance_variable_set('@filter_service', filter)

get :list_with_user_filter

assert_equal [quips(:quip_1)], assigns(:quips)

assert_response :success

assert_template 'list_with_user_filter'

end

9. http://onestepback.org/software/flexmock/

TESTING INTERACTIONS WITH MOCK OBJECTS 228

On line 2, the flexmock method creates a mock object. The argument

is a name that will be used in error messages. In the Java version,

the mock had to have a specific interface so jMock could know what

methods the mock should simulate. Since Ruby is dynamically typed,

we do not specify any specific module or class for the mock.

On line 3, we set the expectations for the mock. FlexMock takes advan￾tage of Ruby’s blocks to set expectations through a recorder object. On

line 4, the block parameter m is a recorder. Instead of saying m.should_

expect.filter, we can simply say m.filter; the should_expect is implicit. Flex￾Mock’s matching of parameters takes advantage of Ruby’s case equality

operator (===). So, the first argument to filter must be an instance of

Array. This array will be the result of Quip.find(:all), and we could have

chosen to match it exactly by instantiating the entire collection in the

test. The second argument nil matches the user’s filtering preferences,

which are initially nil.

On line 6, we set the controller’s @filter_serviceto our mock filter. By

calling instance_variable_set, we avoid the requirement that the controller

provide a setter for @filter_service. There is no call to verify at the end of

the method; FlexMock mocks verify automatically at the end of the test.

Ruby’s blocks and case equality make it easy to define flexible argu￾ment matching. Imagine that we wanted to verify that none of the quips

passed to the @filter_service has non-nil text. FlexMock would handle

this with FlexMock.on:

Download code/rails_xt/test/functional/quips_controller_test.rb

matcher = FlexMock.on {|args| Array === args && args.all? {|a| a.text}}

filter.should_expect do |m|

m.filter(matcher,nil).returns([quips(:quip_1)])

end

The previous tests demonstrates another advantage of mock objects.

Mock objects allow you to test interactions with code that does not exist

yet. In testing the QuipsController, we never create a real filter service. At

the time of this writing, there is no real filter service. This decoupling

lets teams of developers work on related subsystems without having to

wait for completed implementations of every object.

The mock objects in this section replace objects not under test and ver￾ify that those objects are called in an appropriate fashion. Sometimes

you want to replace objects not under test, but you don’t care how they

are called. This subset of mock object capability is provided by stub

objects.

REDUCING DEPENDENCIES WITH STUB OBJECTS 229

7.8 Reducing Dependencies with Stub Objects

It is all too easy to write fragile tests that depend on other classes. Think

about how you might test this simple controller method:

Download code/people/app/controllers/people_controller.rb

def create

@person = Person.new(params[:person])

if @person.save

flash[:notice] = 'Person was successfully created.'

redirect_to :action => 'list'

else

render :action => 'new'

end

end

To test both branches of the code, you will need a valid Person and

an invalid Person. The problem is that you are supposed to be testing

PersonController, not Person. If you pick valid and invalid arguments for

the real Person class, you introduce a dependency on Person. This is a

maintenance headache. When you change Person, you will break the

PersonTest (OK), but you will also break the PersonControllerTest (aargh).

To avoid this problem, we can test a stub version of Person. The stub stub

replaces Person with behavior that we define locally, breaking the exter￾nal dependency. This probably sounds similar to the mock objects from

the previous section, and it is. In fact, we will use the same library for

stubs, FlexMock. Here is a stub-based test for creating a Person:

Download code/people/test/functional/people_controller_test.rb

def test_create_succeeds

flexstub(Person).should_receive(:new).and_return {

flexmock('person') do |m|

m.should_receive(:save).and_return(true)

end

}

post :create

assert_response :redirect

assert_redirected_to :action => 'list'

end

On line 2, flexstub temporarily modifies the behavior of Person. For the

remainder of this test, calls to Person.new will invoke this block of code

instead. On line 3 we mock an instance of Person, and on line 4 we cause

save to always succeed. This test method will test how the controller

handles a successful Person create, regardless of how the real Person

class works.

Tải ngay đi em, còn do dự, trời tối mất!