我可以 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/