【发布时间】:2020-08-20 08:59:53
【问题描述】:
在我们的 Rails 应用程序中,我们有一个第三方 API(使用 Thrift),我们用类进行包装,这些类可以使用多种方法从同一实例中检索数据,然后将该数据添加到实例变量/访问器中。
例如,我们有一个像这样的BookManager 类:
class BookManager
attr_accessor :token, :books, :scope, :total_count
def initialize(token, scope, attrs={})
@token = token
@scope = scope
@books = []
@total_count = 0
end
# find all books
def find_books
@books = API.find_books(@token, @scope)
@total_count = @books.count
self
end
# find a single book by book_id
def find_book_by_id(book_id)
@books = API.find_book_by_id(@token, @scope, book_id)
self
end
# find a single book by author_id
def find_book_by_author_id(author_id)
@books = API.find_book_by_author_id(@token, @scope, author_id)
self
end
end
所以在这里我们可以通过book_id 或author_id 获取书籍列表或一本书,然后API 将返回数据,我们的类实例将拥有这些书籍。
这样构建这个类的主要原因是因为 API 为每个数据实体设计了一个端点,我们必须使用多种方法来获取整个数据集,例如,如果我们想检索作者对于书籍,我们会使用如下方法:
def with_authors(&block)
books.each do |book|
book.author = API.find_author_by_id(@token, @scope, book.author_id, &block)
end
self
end
该类在我们的应用程序中使用如下:
book_manger = BookManager.new(current_user.token, params[:scope])
.find_book_by_id(params[:id])
@book = book_manger.books.first
或者如果我们也想要作者,我们会链接方法:
book_manger = BookManager.new(current_user.token, params[:scope])
.find_book_by_id(params[:id])
.with_authors
@book = book_manger.books.first
然后我们可以像这样访问数据:
@book.book_name
@book.author.author_name
希望到目前为止这一切都有意义......
因此,当我们为我们的应用编写 RSpec 测试时,我们希望模拟出这个 BookManager,这样它就不会调用实际的 API。
例如,在这里我创建了书籍的双份,并告诉 RSpec 在调用 find_book_by_id 方法时返回书籍(里面有书籍)。
book = double('book', book_id: 1, book_name: 'Book Name')
books = double('books', books: [book])
allow_any_instance_of(BookManager).to receive(:find_book_by_id).and_return(books)
但是我发现books 访问器总是返回它的默认值[],所以它实际上并没有使用我的模拟在类实例中设置@books。
相反,我不得不模拟 API 本身:
book = double('book', book_id: 1, book_name: 'Book Name')
books = double('books', books: [book])
allow(API).to receive(:find_book_by_id).and_return(books)
然后允许我使用 BookManager... 这可以说是更好的实践,因为它是需要模拟的 API...但是我们的其他一些类有很多嵌套的 API 方法,我希望为了让模拟更简单,只模拟代码中使用的类,而不是下面的嵌套方法...... 我也很好奇我怎么能做到!
我假设 BookManager 的模拟没有按预期工作,因为我已经模拟了该方法(在这种情况下 find_book_by_id) which is what actual sets @booksand therefore the accessor/instance variable is always empty... so in this particular case, using.and_return(books)` 实际上并没有返回书……
似乎我需要做的是返回该类的实例,而不仅仅是 books,但我不确定如何使用 RSpec 模拟来做到这一点。
【问题讨论】:
-
做以下工作:
let(:stub_manager) { instance_double(BookManager, find_book_by_id: books) }allow(BookManager).to receive(:new).and_return(stub_manager) -
您可以根据规范的需要为方法定义一个fake implementation 吗?喜欢:
allow(book_manager).to receive(:find_book_by_id) { |id| book_manager.instance_variable_set(:'@books', books); book_manager }? -
为努力编写一个清晰、结构良好的问题,并使用完美数量的代码来说明问题而受到支持。谢谢!
标签: ruby-on-rails ruby rspec