我有一个 Sidekiq 工作人员,它通过外部 API 获取一些数据。我正在尝试编写测试以确保该工作人员的设计和功能正常。工作人员抓取一个本地模型实例并检查模型上的两个字段。如果其中一个字段是nil
,它会将另一个字段发送到远程 API。
这是工人代码:
class TokenizeAndVectorizeWorker
include Sidekiq::Worker
sidekiq_options queue: 'tokenizer_vectorizer', retry: true, backtrace: true
def perform(article_id)
article = Article.find(article_id)
tokenizer_url = ENV['TOKENIZER_URL']
if article.content.nil?
send_content = article.abstract
else
send_content = article.content
end
# configure Faraday
conn = Faraday.new(tokenizer_url) do |c|
c.use Faraday::Response::RaiseError
c.headers['Content-Type'] = 'application/x-www-form-urlencoded'
end
# get the response from the tokenizer
resp = conn.post '/tokenize', "content=#{URI.encode(send_content)}"
# the response's body contains the JSON for the tokenized and vectorized article content
article.token_vector = resp.body
article.save
end
end
我想编写一个测试以确保如果文章内容为零,则文章摘要就是发送以进行编码的内容。
我的假设是,这样做的“正确”方法是模拟法拉第的响应,以便我期望对特定输入的特定响应。通过创建包含nil
内容和摘要的文章,x
我可以模拟对发送x
到远程 API 的响应,并模拟对发送nil
到远程 API 的响应。我还可以创建一篇文章,其中x
包含摘要、z
内容和模拟响应z
。
我写了一个一般模拟法拉第的测试:
it "should fetch the token vector on ingest" do
# don't wait for async sidekiq job
Sidekiq::Testing.inline!
# stub Faraday to return something without making a real request
allow_any_instance_of(Faraday::Connection).to receive(:post).and_return(
double('response', status: 200, body: "some data")
)
# create an attrs to hand to ingest
attrs = {
data_source: @data_source,
title: Faker::Book.title,
url: Faker::Internet.url,
content: Faker::Lorem.paragraphs(number: 5).join("<br>"),
abstract: Faker::Book.genre,
published_on: DateTime.now,
created_at: DateTime.now
}
# ingest an article from the attrs
status = Article.ingest(attrs)
# the ingest occurs roughly simultaneously to the submission to the
# worker so we need to re-fetch the article by the id because at that
# point it will have gotten the vector saved to the DB
@token_vector_article = Article.find(status[1].id)
# we should've saved "some data" as the token_vector
expect(@token_vector_article.token_vector).not_to eq(nil)
expect(@token_vector_article.token_vector).to eq("some data")
end
但这模拟了法拉第 100% 的使用:post
。在我的特殊情况下,我不知道如何模拟:post
特定身体的反应......
我也有可能要测试这一切都是错误的。相反,我可以测试我们是否发送了正确的内容(测试应该检查法拉第发送的内容)并完全忽略正确的响应。
测试该工作人员是否做正确事情的正确方法是什么(发送内容,或者如果内容为 nil 则发送摘要)?是测试发送的内容,还是测试我们返回的内容以反映发送的内容?
如果我应该测试返回的内容以反映正在发送的内容,我如何根据发送给它的东西的价值来模拟法拉第的不同响应/
** 稍后添加注释 **
我做了更多的挖掘和思考,好吧,让我测试一下我正在发送我期望的请求,并且我正在正确处理响应。所以,我尝试使用 webmock。
it "should fetch token vector for article content when content is not nil" do
require 'webmock/rspec'
# don't wait for async sidekiq job
Sidekiq::Testing.inline!
request_url = "#{ENV['TOKENIZER_URL']}/tokenize"
# webmock the expected request and response
stub = stub_request(:post, request_url)
.with(body: 'content=y')
.to_return(body: 'y')
# create an attrs to hand to ingest
attrs = {
data_source: @data_source,
title: Faker::Book.title,
url: Faker::Internet.url,
content: "y",
abstract: Faker::Book.genre,
published_on: DateTime.now,
created_at: DateTime.now
}
# ingest an article from the attrs
status = Article.ingest(attrs)
# the ingest occurs roughly simultaneously to the submission to the
# worker so we need to re-fetch the article by the id because at that
# point it will have gotten the vector saved to the DB
@token_vector_article = Article.find(status[1].id)
# we should have sent a request with content=y
expect(stub).to have_been_requested
# we should've saved "y" as the token_vector
expect(@token_vector_article.token_vector).not_to eq(nil)
expect(@token_vector_article.token_vector).to eq("y")
end
但我认为 webmock 并没有在 sidekiq 工作中被选中,因为我明白了:
1) Article tokenization and vectorization should fetch token vector for article content when content is not nil
Failure/Error: expect(stub).to have_been_requested
The request POST https://zzzzz/tokenize with body "content=y" was expected to execute 1 time but it executed 0 times
The following requests were made:
No requests were made.
============================================================
如果我尝试webmock/rspec
在其他任何地方包含,例如,在我的文件的开头,随机的东西就会开始爆炸。例如,如果我在这个规范文件的开头有这些行:
require 'spec_helper'
require 'rails_helper'
require 'sidekiq/testing'
require 'webmock/rspec'
然后我得到:
root@c18df30d6d22:/usr/src/app# bundle exec rspec spec/models/article_spec.rb:174
database: test
Run options: include {:locations=>{"./spec/models/article_spec.rb"=>[174]}}
There was an error creating the elasticsearch index for Article: #<NameError: uninitialized constant Faraday::Error::ConnectionFailed>
There was an error removing the elasticsearch index for Article: #<NameError: uninitialized constant Faraday::Error::ConnectionFailed>
我猜这是因为测试套件正在尝试初始化东西,但 webmock 正在干扰......