sql - RSpec:如何现在模拟 SQL()

标签 sql ruby-on-rails rspec mocking

我可以 mock Time.now与伟大的 timecop gem 。

Time.now
 => 2018-05-13 18:04:46 +0300

Timecop.travel(Time.parse('2018.03.12, 12:00'))

Time.now
 => 2018-03-12 12:00:04 +0300

TeacherVacation.first.ends_at
Thu, 15 Mar 2018 12:00:00 MSK +03:00

TeacherVacation.where('ends_at > ?', Time.now).count
1

但是(显然)这在使用 NOW() 时不起作用在查询中:
TeacherVacation.where('ends_at > NOW()').count
0

我可以 mock NOW()以便它会在一段时间内返回结果?

最佳答案

Timecop是一个伟大的 gem !我建议使用 Timecop.freeze而不是为您的实例旅行;您希望保持测试的确定性。

据我所知,似乎没有办法模拟 SQL 的函数。像 Postgres 这样的一些语言允许重载函数,但是您仍然需要一种插入的方法,而且似乎没有一种方法可以在 SQL 中使用环境变量。

一位同事似乎确信您实际上可以放弃系统/语言功能并创建自己的功能,但我担心在您这样做之后如何恢复它们。尝试走那条路听起来很痛苦。

解决方案?

这是我今天在解决这个问题时提出的几个“解决方案”。注意:老实说,我真的不关心它们,但是如果它进行了测试 ¯\_(ツ)_/¯ 它们至少提供了一种让事情“工作”的方法。

不幸的是没有时髦的 gem 来控制 SQL 中的时间。我想你会需要一些疯狂的东西,比如数据库的插件,一个黑客,一个钩子(Hook),一个中间人,一个你可以欺骗 SQL 认为系统时间是别的东西的容器。不幸的是,这些 hack 想法中没有一个肯定是可移植的/平台不可知的。

显然有一些方法可以set time in a docker container ,但这听起来像是本地测试的痛苦开销,并且不适合要设置的每次测试时间的粒度。

另一件要注意的事情,对我来说,我们正在运行大型复杂的原始 SQL 查询,这就是为什么当我运行 SQL 文件进行测试时,我可以有正确的日期很重要,否则我只会像你提到的那样通过 activerecord 来做.

字符串插值

我在一些正在运行的大型查询中遇到了这个问题。
如果您需要推送一些环境变量,这肯定会有所帮助,并且您可以根据需要注入(inject)自己的“current_date”。如果您需要在多个查询中使用特定时间,这也会有所帮助。

my_query.rb

<<~HEREDOC
  SELECT *
  FROM #{@prefix}.my_table
  WHERE date < #{@current_date} - INTERVAL '5 DAYS'
HEREDOC

sql_runner.rb

class SqlRunner
  def initialize(file_path)
    @file_path = file_path
    @prefix = ENV['table_prefix']
    @current_date = Date.today
  end

  def run
    execute(eval(File.read @file_path))
  end

  private

  def execute(sql)
    ActiveRecord::Base.connection.execute(sql)
  end
end

肮脏的更新

这个想法是更新来自 ruby​​ land 的值,将您的“时间限制”时间插入数据库以覆盖 SQL DB 生成的值。您可能需要在更新时发挥创意,例如查询大于给定时间的时间,但不针对您将要更新行的 timecop 时间。

我不关心这个方法的原因是因为它最终感觉就像你只是在测试 activerecord 的功能,因为你不依赖数据库来设置它应该设置的值。您可能在 SQL 中进行了计算,然后在测试中重新创建以将某个值设置为正确的日期,然后您不再在 SQL 中进行计算,因此您甚至没有实际测试它。

large_insert.sql

INSERT INTO some_table (
  name,
  created_on
)
SELECT
  name,
  current_date
FROM projects
JOIN people ON projects.id = people.project_id

插入规范.rb

describe 'insert_test.sql' do
  ACTUAL_DATE = Date.today
  LARGE_INSERT_SQL = File.read('sql/large_insert.sql')

  before do
    Timecop.freeze Date.new(2018, 10, 28)
  end

  after do
    Timecop.return
  end

  context 'populated same_table' do
    before do
      execute(LARGE_INSERT_SQL)
      mock_current_dates(ACTUAL_DATE)
    end

    it 'has the right date' do
      expect(SomeTable.last.created_on).to eq(Date.parse('2018.10.28')
    end
  end

  def execute(sql_command)
    ActiveRecord::Base.connection.execute(sql_command)
  end

  def mock_current_dates(actual_date)
    rows = SomeTable.where(created_on: actual_date)
    # Use our timecop datetime
    rows.update_all(created_on: Date.today)
  end

有趣的警告 : 规范包装在它们自己的事务中(您可以关闭它,但这是一个很好的功能)所以如果您的 SQL 中有一个事务,您将需要编写代码来删除它的规范,或者让您的运行程序包装您的如果需要,请在交易中编码。它们会运行,但随后您的 SQL 将终止规范事务,您将度过一段糟糕的时光。您可以创建一个 spec/support如果您在测试期间走清理路线,为了帮助解决这个问题,如果我在一个较新的项目中,我会编写一个运行程序,如果需要,将查询包装在事务中 - 即使这在SQL 文件#abstraction。

也许有一些东西可以让你设置系统时间,但是修改系统的实际时间听起来很可怕。

关于sql - RSpec:如何现在模拟 SQL(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50317570/

相关文章:

php - MySql 获取产品搜索结果中的类别数

mysql - 使用 MySQL 将 LIKE 与存储过程参数连接起来

java - 这个数据库更新命令的计算是否正确?

ruby-on-rails - 在哪里放置 Rails Controller 的 Ruby 辅助方法?

ruby-on-rails - 如何缩小 Rails 中的 JSON 输出?

ruby-on-rails - 将 HAML 与 RSPEC 集成

ruby-on-rails - 测试 Redcarpet Markdown 助手

php - 如何将 18 个变量传递到一个函数中以验证 PHP/MySQL 交互

c++ - 使用 Ruby on Rails 进行复杂算法

ruby-on-rails-3 - capybara 访问方法无效