Rails tests: Net::HTTP post – empty response.body in Rspec when fetching external site

huangapple go评论62阅读模式
英文:

Rails tests: Net::HTTP post - empty response.body in Rspec when fetching external site

问题

奇怪的RSpec行为:我从测试中发送一个POST请求。

context "当网站管理员时" do
    let(:question) { create(:question) }
    let(:user) { create(:user, :webmaster) }

    describe "从管理员表单创建问题" do
      it "不成功" do
        sign_in(user)
        region = "1c5b2444-70a0-4932-980c-b4dc0d3f02b5"
        params = { question: {
          phone: 123,
          region: region
        }}

        # 同一个应用程序
        post :create_from_webmaster_modal, params: params
        expect(response).to have_http_status(:ok)
      end
    end
  end

它调用了同一应用程序中的控制器

# 这个控制器在同一应用程序中
def create_from_webmaster_modal
  ...
    # 获取外部站点
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    response = http.post(uri.path, params.to_json, headers)

    # 从RSpec调用时,响应体为空
    json_response = JSON.parse(response.body) 
  ...
end

... 从外部API获取数据。
在控制器中的所有情况下,都会从外部API获取数据。

在控制台(rails c)中,在所有环境(生产、开发、测试)中,一切都可以正常运行,没有错误。

在运行以下命令时:

RAILS_ENV=test bundle exec rspec

响应体为空,响应是Net::HTTPOK

传递给http.post(...)的所有参数都是正确的。

Rails 5.2
Ruby - 2.5(是的,这是一个旧的应用程序)
Rspec - 3.10

发生了什么?

英文:

Strange RSpec behaviour: I send a post request from a test.

context "when webmaster" do
    let(:question) { create(:question) }
    let(:user) { create(:user, :webmaster) }

    describe "creating question from admin form" do
      it "not success" do
        sign_in(user)
        region = "1c5b2444-70a0-4932-980c-b4dc0d3f02b5"
        params = { question: {
          phone: 123,
          region: region_id
        }}

        # Same application
        post :create_from_webmaster_modal, params: params
        expect(response).to have_http_status(:ok)
      end
    end
  end

It calls the application controller (same application)

# This controller in the same application
def create_from_webmaster_modal
  ...
    # Fetch external site
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    response = http.post(uri.path, params.to_json, headers)

    # When called from RSpec, body is empty
    json_response = JSON.parse(response.body) 
  ...
end

... which fetches data from an external API.
In all cases, code in controller fetches data from external API.

In console (rails c) in all environments (production, development, test) everything works without errors.

In the case of running

RAILS_ENV=test bundle exec rspec

response.body is empty and the response is Net::HTTPOK

All parameters coming into http.post(...) are correct.
<pre>
Rails 5.2
Ruby - 2.5 (yes, it's an old application (( )
Rspec - 3.10
</pre>
Whats happend?

Rails tests: Net::HTTP post – empty response.body in Rspec when fetching external site

答案1

得分: 1

回答这个问题很困难,因为我们不知道在发出请求时发生了什么。您依赖于外部服务在测试套件的任何运行期间可用,并且依赖于其响应的一致性。这违反了FIRST原则

您可以尝试通过在控制器操作中放置binding.irb来对该规范进行故障排除:

# 同一应用程序中的此控制器
def create_from_webmaster_modal
  ...
    # 获取外部站点
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    response = http.post(uri.path, params.to_json, headers)

    binding.irb

    # 从rspec调用时,body为空
    json_response = JSON.parse(response.body) 
  ...
end

当您运行测试时,它将在该点打开一个REPL,并且您可以使用ls命令检查该方法范围内的所有内容。您应该检查在请求中使用的所有变量和方法是否符合您的预期,并且您可以尝试在REPL中重新运行response = http.post(uri.path, params.to_json, headers)命令,以确保它连接到您期望的地方。

一个问题是我们不知道在运行任何给定的shell命令时,您的shell或应用程序环境看起来如何。您启动Rails控制台的方式可能会注入一些环境变量,但启动RSpec的方式可能没有注入这些变量。确保您始终以bundle exec一致地启动它们,例如:

# 在测试环境中启动控制台以运行请求
RAILS_ENV=test bundle exec rails console

# 并在测试环境中启动rspec以运行请求
RAILS_ENV=test bundle exec rspec

您还应该检查您的spec_helper.rbrails_helper.rb,以确保您的预期配置没有被覆盖。

但所有这些都不是重点:您不应该在单元测试期间测试外部服务的可用性或响应状态。测试您的代码,而不是外部服务。对于这样的情况,您应该使用像VCR这样的gem来记录和重放远程请求,以便您的规范始终可重复、一致和快速。或者,您可以简单地模拟它

英文:

It's difficult to answer this question because we don't know what's happening when that request is being made. You're relying on an external service to be available during any run of your test suite and you're relying on it being consistent in its response. That's a violation of FIRST principles.

You can try to troubleshoot the spec by putting binding.irb inside your controller action:

# This controller in the same application
def create_from_webmaster_modal
  ...
    # Fetch external site
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    response = http.post(uri.path, params.to_json, headers)

    binding.irb

    # When called from rspec, body is empty
    json_response = JSON.parse(response.body) 
  ...
end

When you run the test it will open a REPL at that point and you can use it to inspect everything in the scope of that method with the ls command. You should check that all the variables and methods that are used in the request are what you expect them to be, and you can attempt to re-run the response = http.post(uri.path, params.to_json, headers) command in the REPL to ensure that it's making the connection that you expect it to.

One issue is that we don't know what your shell or application environments look like when you run any given shell command. It's possible that the way that you start the Rails console is injecting some environment variables but the way that you start RSpec is not injecting those variables. Make sure that you're starting them consistently and with bundle exec, e.g.:

# Start the console in the test environment to run the request
RAILS_ENV=test bundle exec rails console

# And start rspec in the test environment to run the request
RAILS_ENV=test bundle exec rspec

You should also check your spec_helper.rb and rails_helper.rb to ensure that your expected configuration isn't being overwritten.

But all of this is besides the point: you shouldn't be testing the availability or response status of an external service during your unit tests. Test your code, not external services. For something like this you should be using a gem like VCR to record and replay remote requests so that your specs are always repeatable and consistent and fast. Alternatively, you can just mock it.

答案2

得分: 0

免责声明:我没有足够的声望来评论,这个帖子理论上应该是一条评论

我在一个全新的Rails应用上进行了测试,添加了HTTParty并请求与您在环境中相同的URL:响应体不为空。

我认为问题不在于HTTParty。

您是否在Rails环境中有任何初始化程序或库,可能会禁用仅用于测试环境的网络连接?
例如,像webmock之类的东西?这是webmock的用法以及它如何返回空响应体。

也许在"spec_helper.rb"级别,这样您只能在测试套件中看到问题?

这可能解释了任何URL获取都返回HTTP成功代码以及空响应体的情况。

您可以尝试在HTTParty.get中使用一个无效的URL吗?

比如 HTTParty.get('https://invalidexample.com/').body
如果您仍然得到一个200 OK和空响应体,那么您的某个库可能在TEST ENV中干扰外部请求

(正如其他答案中指出的那样,这应该是测试代码的最佳方式,没有真正的连接)

英文:

Disclaimer : I don't have enough reputation to comment, this post should ideally be a comment

I tested on a brand new rails app, adding HTTParty and requesting the same url you do in your env : the body is not empty
Rails tests: Net::HTTP post – empty response.body in Rspec when fetching external site

I think the problem is not in httparty

Do you happen to have any initializer or library in your Rails env that might disable net connect only for test environment ?
Something such as webmock or so ? Here's webmock usage and how it returns empty body

Maybe at the "spec_helper.rb" level that way you only see the problem in your test suite ?

Rails tests: Net::HTTP post – empty response.body in Rspec when fetching external site
This could explain any URL fetch to return http success code as well as empty body

Can you try an invalid URL in your HTTParty.get?

such as HTTParty.get(&#39;https://invalidexample.com/&#39;).body
If you still have a 200 OK and empty body you have a lib somewhere tampering with external requests only on TEST ENV

(as pointed in other answers this should be the best way to test your code anyway, no real connection)

huangapple
  • 本文由 发表于 2023年2月24日 04:05:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75549806.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定