使用 Mocha 模拟/存根应用程序控制器方法(使用 Shoulda、Rails 3)
在为控制器编写功能测试时,我遇到了一个场景,其中我有一个 before_filter 从数据库请求我的一个测试所需的一些信息。我正在使用 Factory_girl 生成测试数据,但我想避免在未明确需要时访问数据库。我还想避免在这里测试我的 before_filter 方法(我计划在单独的测试中测试它)。据我了解,模拟/存根是实现此目的的方法。
我的问题是,在这种情况下模拟/存根此方法的最佳方法是什么。
我的 before 过滤器方法根据 URL 中找到的子域在数据库中查找站点并设置实例变量要在控制器中使用:
#application_controller.rb
def load_site_from_subdomain
@site = Site.first(:conditions => { :subdomain => request.subdomain })
end
我的控制器将此方法用作 before_filter:
# pages_controller.rb
before_filter :load_site_from_subdomain
def show
@page = @site.pages.find_by_id_or_slug(params[:id]).first
respond_to do |format|
format.html { render_themed_template }
format.xml { render :xml => @page }
end
end
如您所见,它依赖于要设置的 @site
变量(通过 before_filter)。然而,在测试过程中,我想让测试假设 @site
已设置,并且它至少有 1 个关联页面(由 @site.pages
找到) )。我想稍后测试我的 load_site_from_subdomain
方法。
这是我在测试中得到的结果(使用 Shoulda 和 Mocha):
context "a GET request to the #show action" do
setup do
@page = Factory(:page)
@site = Factory.build(:site)
# stub out the @page.site method so it doesn't go
# looking in the db for the site record, this is
# used in this test to add a subdomain to the URL
# when requesting the page
@page.stubs(:site).returns(@site)
# this is where I think I should stub the load_site_from_subdomain
# method, so the @site variable will still be set
# in the controller. I'm just not sure how to do that.
@controller.stubs(:load_site_from_subdomain).returns(@site)
@request.host = "#{ @page.site.subdomain }.example.com"
get :show, :id => @page.id
end
should assign_to(:site)
should assign_to(:page)
should respond_with(:success)
end
这使我的测试结果出现错误,告诉我 @site
为零。
我觉得我正在以错误的方式处理这件事。我知道只需 Factory.create 站点即可很容易,因此它存在于数据库中,但正如我之前所说,我想减少数据库使用量以帮助保持测试速度。
While writing functional tests for a controller, I came across a scenario where I have a before_filter requesting some information from the database that one of my tests requires. I'm using Factory_girl to generate test data but I want to avoid hitting the database when its not explicitly needed. I'd also like to avoid testing my before_filter method here (I plan to test it in a separate test). As I understand, mocking/stubbing is the way to accomplish this.
My question is, what is the best way to mock/stub this method in this scenario.
My before filter method looks for a site in the db based on a subdomain found in the URL and sets an instance variable to be used in the controller:
#application_controller.rb
def load_site_from_subdomain
@site = Site.first(:conditions => { :subdomain => request.subdomain })
end
My controller that uses this method as a before_filter:
# pages_controller.rb
before_filter :load_site_from_subdomain
def show
@page = @site.pages.find_by_id_or_slug(params[:id]).first
respond_to do |format|
format.html { render_themed_template }
format.xml { render :xml => @page }
end
end
As you can see, it relies on the @site
variable to be set (by the before_filter). During testing however, I'd like to have the test assume that @site
has been set, and that it has at least 1 associated page (found by @site.pages
). I'd like to then test my load_site_from_subdomain
method later.
Here is what I have in my test (using Shoulda & Mocha):
context "a GET request to the #show action" do
setup do
@page = Factory(:page)
@site = Factory.build(:site)
# stub out the @page.site method so it doesn't go
# looking in the db for the site record, this is
# used in this test to add a subdomain to the URL
# when requesting the page
@page.stubs(:site).returns(@site)
# this is where I think I should stub the load_site_from_subdomain
# method, so the @site variable will still be set
# in the controller. I'm just not sure how to do that.
@controller.stubs(:load_site_from_subdomain).returns(@site)
@request.host = "#{ @page.site.subdomain }.example.com"
get :show, :id => @page.id
end
should assign_to(:site)
should assign_to(:page)
should respond_with(:success)
end
This leaves me with an error in my test results telling me that @site
is nil.
I feel like I'm going about this the wrong way. I know it would be easy to simply just Factory.create the site so it exists in the db, but as I said earlier, I'd like to reduce the db usage to help keep my tests speedy.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
尝试删除“Site.first”,因为它是您需要存根的 @site var 的设置,而不是 before_filter 返回的 var。
Try stubbing out 'Site.first' since it the the setting of the @site var that you need to stub and not the returned var from the before_filter.
您的
@site
为nil
的原因是您的load_site_from_subdomain
为@site
进行了值分配 - 它确实不返回任何值,因此您对load_site_from_subdomain
的存根不会将值分配给@site
。有两种解决方法:第一种方法:
将
load_site_from_subdomain
更改为仅执行返回值:然后删除
before_filter :load_site_from_subdomain
和将您的show
更改为:然后在测试中进行存根:
确保我们的
@site
通过load_site_from_subdomain
间接存根第二种方式
为了存根
Site.first
,我不太喜欢这种方法,因为在功能测试中,我们并不真正关心如何检索模型,而是响应的行为
。不管怎样,如果你想走这条路,你可以在测试中把它去掉:The reason why your
@site
isnil
because yourload_site_from_subdomain
does the value assignment for@site
-- it does not return any value hence your stubbing forload_site_from_subdomain
simply doesn't assign the value to@site
. There are two work-arounds for this:First way:
Change
load_site_from_subdomain
to just do a return value:and then remove the
before_filter :load_site_from_subdomain
and change yourshow
to:And then do the stubbing in the test:
that ensure our
@site
is stubbed indirectly viaload_site_from_subdomain
Second way
To stub the
Site.first
, I do not really like this approach as in functional test, we do not really care about how the model is retrieved but the behaviour of therespond
. Anyway, if you feel like going this path, you could stub it out in your test: