【问题标题】:Testing URL (regex) validation with RSpec使用 RSpec 测试 URL(正则表达式)验证
【发布时间】:2017-06-27 21:02:14
【问题描述】:

Rails 4.2 应用程序有多个模型,其属性包含 URL。 URL 验证在模型上使用validates :website_url, format: { with: /\A(https?|ftp):\/\/(-\.)?([^\s\/?\.#-]+\.?)+(\/[^\s]*)?\z/i } 完成。

我需要使用 RSpec 3.5 测试 URL 验证。重要的是要确保一些众所周知的 XSS 模式没有通过验证,并且最常用的 URL 模式通过验证。

理想情况下,我想避免为我正在测试的每个有效和无效 URL 添加一个测试,这样rspec -fd 输出就不会受到污染。但是,这可能需要创建两个测试(一个用于有效 URL,另一个用于无效 URL)并向每个测试添加多个期望(每个 URL 一个期望),这似乎不是一个好主意。

到目前为止,我想出的最佳解决方案是以下共享示例。 您能想出更好的方法来彻底测试 URL 验证吗?

RSpec.shared_examples "url validation" do |attribute|
  INVALID_URLS = [
      "invalidurl",
      "inval.lid/urlexample",
      "javascript:dangerousJs()//http://www.validurl.com",
      # Literal array is required for \n to be parsed
      "http://www.validurl.com\n<script>dangerousJs();</script>"
  ]

  VALID_URLS = [
      "http://validurl.com",
      "https://validurl.com/blah_blah"
  ]

  context "with invalid URLs in #{attribute}" do
    INVALID_URLS.each do |url|
      it "is invalid with #{url}" do
        object = FactoryGirl.build(factory_name(subject), attribute => url)
        object.valid?
        expect(object.errors[attribute]).to include("is invalid")
      end
    end
  end

  context "with valid URLs in #{attribute}" do
    VALID_URLS.each do |url|
      it "is valid with #{url}" do
        object = FactoryGirl.build(factory_name(subject), attribute => url)
        expect(object).to be_valid
      end
    end
  end

在模型规格内:

include_examples "url validation", :website_url

编辑:为有效和无效的 URL 添加了 context,因此 rspec -fd 输出可以更好地组织,即使大量 URL 验证测试以随机顺序执行。

【问题讨论】:

    标签: ruby-on-rails ruby validation testing rspec


    【解决方案1】:

    您的解决方案还不错。继续用更多shared_examples 分解它对我来说是有意义的:

    RSpec.shared_examples "an invalid URL for attribute" do |url, attribute|
      let(:object) { FactoryGirl.build(factory_name(subject), attribute => url) }
      before(:each) { object.valid? }
      specify { expect(object.errors[attribute]).to include("is invalid") }
    end
    
    RSpec.shared_examples "a valid URL for attribute" do |url, attribute|
      let(:object) { FactoryGirl.build(factory_name(subject), attribute => url) }
      specify { expect(object).to be_valid }
    end
    
    RSpec.shared_examples "URL validation" do |attribute|
      [ "invalidurl",
        "inval.lid/urlexample",
        # ...
      ].each do |url|
        it_behaves_like "a valid URL for attribute", url, attribute
      end
    
      [ "http://validurl.com",
        "https://validurl.com/blah_blah"
      ].each do |url|
        it_behaves_like "an invalid URL for attribute", url, attribute
      end
    end
    

    重复的let(:object) 困扰着我,但我没有立即的解决方案。

    【讨论】:

    • 感谢您的回答。我也尝试过并且无法避免重复创建对象。即使在同一个 shared_examples 块中,将循环内的 URL 传递到 let 块也很麻烦。
    • @BrunoFacca 澄清一下,上述解决方案是否适用于您的用例?如果您有任何问题,请告诉我。
    • 它有效。但是,您能否解释一下以这种方式分解它的好处(创建 3 个共享示例)?在我看来,虽然这种方法很聪明/复杂,但它引入了额外的复杂性,而不会减少重复或 LOC。根据您的经验,我可能遗漏了一些东西。
    • @BrunoFacca 你说得对,这里没有 巨大的 差异。我喜欢这种方法,因为参数化示例组更具声明性。看起来这些测试是一个更大的套件的一部分,它的设计方式有点强迫你的手(ergo object 而不是subject)。您可能想在Code Review Stack Exchange 上发布您的代码的更大部分,看看他们是否有任何提示。
    • 感谢您的帮助。会这样做。
    猜你喜欢
    • 1970-01-01
    • 2016-10-15
    • 2016-06-30
    • 2014-10-21
    • 2021-02-24
    • 1970-01-01
    • 2012-11-08
    • 1970-01-01
    相关资源
    最近更新 更多