RSpec 3:如何从我们尚未 build/add 的代码中存根方法和常量?
RSpec 3: How to stub methods and constants from code we have yet to build/add?
我们正在重构一个名为 DataSourceIntegrations 的 Ruby 应用程序,该应用程序来自我们构建的名为 DBQuery 的 gem。我正在将一些 DBQuery 代码迁移到 DataSourceIntegrations 中。我正在构建的部分依赖于 DBQuery,它将在单独的步骤中添加。
同时,我需要编写 RSpec 测试来验证 DBQuery 代码是否被正确调用,所有这些都没有 DBQuery。
我有的是:
代码—
Gem代码—
module DBQuery
class Query
MAX = 1000
def retrieve_users
# Returns an array of user IDs
end
end
end
申请代码—
module Integration
def initialize
@query = DBQuery::Query.new
end
end
module Integration
class WhosebugIntegration
include Integration
def query
users = []
while (users < DBQuery::Query::MAX) do
# Creates a users buffer
users.push @query.retrieve_users(users_buffer)
end
end
end
end
测试—
describe Integration::WhosebugIntegration do
let(:db_query) { double('DBQuery::Query') }
before do
stub_const('DBQuery::Query::MAX', 1000)
allow(db_query).to receive(:new).and_return(db_query)
allow(db_query).to receive(:retrieve_users).and_return([1000, 1001, 1002])
end
it 'queries without error' do
expect { WhosebugIntegration.new.query }.to_not raise_error
end
end
我不知道如何以不需要 DBQuery 的方式存根。我的错误是:
NoMethodError:
undefined method `new' for #<Module:0x007fa7ce561968>
我不知道为什么 DBQuery::Query 被表示为一个模块,或者如何让它识别 "new."
据我了解,您想对 DBQuery::Query
做出期望,而不是在您的代码中定义它。 rspec-mocks 可以先 stub an undefined constant, like you did for DBQuery::Query::MAX
. To stub DBQuery::Query
completely, create a class double 并在你的测试中为它存根一个 const:
db_query__query_class = class_double('DBQuery::Query')
stub_const('DBQuery::Query', db_query__query_class)
这样,您的代码中的 DBQuery::Query
将 return query_class
加倍。然后你可以用它定义一些行为:
query_instance = instance_double('DBQuery::Query')
allow(db_query__query_class).to receive(:new).and_return(query_instance)
allow(db_query).to receive(:retrieve_users).and_return([1000, 1001, 1002])
你仍然需要像 DBQuery::Query::MAX
这样的嵌套常量
stub_const('DBQuery::Query::MAX', 1000)
关于样式,我更喜欢在 let
/let!
语句中放置存根和允许,如下所示:
describe Integration::WhosebugIntegration do
let!(:db_query__query_class) do
class_double('DBQuery::Query').tap do |double|
stub_const('DBQuery::Query', double)
stub_const('DBQuery::Query::MAX', 1000)
allow(double).to receive(:new).and_return(query_instance)
end
end
let(:query_instance) do
instance_double('DBQuery::Query').tap do |double|
allow(double).to receive(:retrieve_users).and_return([1000, 10001, 1002])
end
end
end
我还喜欢将 returned 值放在它们自己的 let
中,这样我就可以轻松更改它们。这是一个完整的工作(和虚拟)示例:
RSpec.configure do |c|
c.around(:context, :protect_with_timeout) do |example|
Timeout::timeout(2) {
example.run
}
end
end
describe Integration::WhosebugIntegration do
let!(:db_query__query_class) do
class_double('DBQuery::Query').tap do |double|
stub_const('DBQuery::Query', double)
stub_const('DBQuery::Query::MAX', max_queries)
allow(double).to receive(:new).and_return(query_instance)
end
end
let(:query_instance) do
instance_double('DBQuery::Query').tap do |double|
allow(double).to receive(:retrieve_users).and_return(retrieved_users)
end
end
let(:max_queries) { 1000 }
let(:retrieved_users) { [1000, 1001, 1002] }
describe '#query' do
subject(:stack_overflow_query) { Integration::WhosebugIntegration.new.query }
it 'queries without error in nominal case' do
expect { stack_overflow_query }.to_not raise_error
end
context 'with 0 users returned' do
let(:retrieved_users) { [] }
it 'does not loop forever', :protect_with_timeout do
pending('not implemented yet...')
stack_overflow_query # will timeout
end
end
context 'with 10 users returned' do
let(:retrieved_users) { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }
it 'calls #retrieve_users 100 times' do
stack_overflow_query
expect(query_instance).to have_received(:retrieve_users).exactly(100).times
end
end
context 'with DBQuery::Query::MAX set to 0' do
let(:max_queries) { 0 }
it 'does not call #retrieve_users at all' do
stack_overflow_query
expect(query_instance).not_to have_received(:retrieve_users)
end
end
end
end
简短的回答是,在 RSpec 的开头:
let(:db_query_class) { class_double('DBQuery::Query') }
let(:db_query) { instance_double('DBQuery::Query') }
before do
stub_const('DBQuery::Query', db_query_class)
allow(db_query_class).to receive(:new).and_return(db_query)
allow(db_query).to receive(:retrieve_users).and_return([1000, 1001, 1002])
stub_const('DBQuery::Query::MAX', 1000)
end
我们正在重构一个名为 DataSourceIntegrations 的 Ruby 应用程序,该应用程序来自我们构建的名为 DBQuery 的 gem。我正在将一些 DBQuery 代码迁移到 DataSourceIntegrations 中。我正在构建的部分依赖于 DBQuery,它将在单独的步骤中添加。
同时,我需要编写 RSpec 测试来验证 DBQuery 代码是否被正确调用,所有这些都没有 DBQuery。
我有的是:
代码—
Gem代码—
module DBQuery
class Query
MAX = 1000
def retrieve_users
# Returns an array of user IDs
end
end
end
申请代码—
module Integration
def initialize
@query = DBQuery::Query.new
end
end
module Integration
class WhosebugIntegration
include Integration
def query
users = []
while (users < DBQuery::Query::MAX) do
# Creates a users buffer
users.push @query.retrieve_users(users_buffer)
end
end
end
end
测试—
describe Integration::WhosebugIntegration do
let(:db_query) { double('DBQuery::Query') }
before do
stub_const('DBQuery::Query::MAX', 1000)
allow(db_query).to receive(:new).and_return(db_query)
allow(db_query).to receive(:retrieve_users).and_return([1000, 1001, 1002])
end
it 'queries without error' do
expect { WhosebugIntegration.new.query }.to_not raise_error
end
end
我不知道如何以不需要 DBQuery 的方式存根。我的错误是:
NoMethodError:
undefined method `new' for #<Module:0x007fa7ce561968>
我不知道为什么 DBQuery::Query 被表示为一个模块,或者如何让它识别 "new."
据我了解,您想对 DBQuery::Query
做出期望,而不是在您的代码中定义它。 rspec-mocks 可以先 stub an undefined constant, like you did for DBQuery::Query::MAX
. To stub DBQuery::Query
completely, create a class double 并在你的测试中为它存根一个 const:
db_query__query_class = class_double('DBQuery::Query')
stub_const('DBQuery::Query', db_query__query_class)
这样,您的代码中的 DBQuery::Query
将 return query_class
加倍。然后你可以用它定义一些行为:
query_instance = instance_double('DBQuery::Query')
allow(db_query__query_class).to receive(:new).and_return(query_instance)
allow(db_query).to receive(:retrieve_users).and_return([1000, 1001, 1002])
你仍然需要像 DBQuery::Query::MAX
stub_const('DBQuery::Query::MAX', 1000)
关于样式,我更喜欢在 let
/let!
语句中放置存根和允许,如下所示:
describe Integration::WhosebugIntegration do
let!(:db_query__query_class) do
class_double('DBQuery::Query').tap do |double|
stub_const('DBQuery::Query', double)
stub_const('DBQuery::Query::MAX', 1000)
allow(double).to receive(:new).and_return(query_instance)
end
end
let(:query_instance) do
instance_double('DBQuery::Query').tap do |double|
allow(double).to receive(:retrieve_users).and_return([1000, 10001, 1002])
end
end
end
我还喜欢将 returned 值放在它们自己的 let
中,这样我就可以轻松更改它们。这是一个完整的工作(和虚拟)示例:
RSpec.configure do |c|
c.around(:context, :protect_with_timeout) do |example|
Timeout::timeout(2) {
example.run
}
end
end
describe Integration::WhosebugIntegration do
let!(:db_query__query_class) do
class_double('DBQuery::Query').tap do |double|
stub_const('DBQuery::Query', double)
stub_const('DBQuery::Query::MAX', max_queries)
allow(double).to receive(:new).and_return(query_instance)
end
end
let(:query_instance) do
instance_double('DBQuery::Query').tap do |double|
allow(double).to receive(:retrieve_users).and_return(retrieved_users)
end
end
let(:max_queries) { 1000 }
let(:retrieved_users) { [1000, 1001, 1002] }
describe '#query' do
subject(:stack_overflow_query) { Integration::WhosebugIntegration.new.query }
it 'queries without error in nominal case' do
expect { stack_overflow_query }.to_not raise_error
end
context 'with 0 users returned' do
let(:retrieved_users) { [] }
it 'does not loop forever', :protect_with_timeout do
pending('not implemented yet...')
stack_overflow_query # will timeout
end
end
context 'with 10 users returned' do
let(:retrieved_users) { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }
it 'calls #retrieve_users 100 times' do
stack_overflow_query
expect(query_instance).to have_received(:retrieve_users).exactly(100).times
end
end
context 'with DBQuery::Query::MAX set to 0' do
let(:max_queries) { 0 }
it 'does not call #retrieve_users at all' do
stack_overflow_query
expect(query_instance).not_to have_received(:retrieve_users)
end
end
end
end
简短的回答是,在 RSpec 的开头:
let(:db_query_class) { class_double('DBQuery::Query') }
let(:db_query) { instance_double('DBQuery::Query') }
before do
stub_const('DBQuery::Query', db_query_class)
allow(db_query_class).to receive(:new).and_return(db_query)
allow(db_query).to receive(:retrieve_users).and_return([1000, 1001, 1002])
stub_const('DBQuery::Query::MAX', 1000)
end