我有以下堆栈
我写了一堆端到端测试来测试我所有的网络服务。问题是其中一些是时间相关的(如“给我最后 X 秒的修改记录”)。
sinon
非常擅长模拟 Node 中所有与时间/日期相关的内容,但是我的 Postgresql 表中有一个 modified
字段,该字段填充了触发器:
CREATE FUNCTION update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.modified = now();
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
问题当然是
sinon
不能覆盖那个now()
函数。
关于如何解决这个问题的任何想法?问题不在于在测试开始时设置特定日期,而是比实时提前时间更快(在我的一项测试中,我想更改数据库中的一些内容,将“当前时间”提前一天,更改数据库中的更多内容并进行网络服务调用以查看结果)。
我自己可以想出一些解决方案,但它们都涉及更改应用程序代码并使其变得不那么优雅。我认为您的应用程序代码不应受到您想要测试它这一事实的影响。
我发现this very neat gist它提供了一个伪造的
NOW()
函数,它存在于一个单独的模式中。您将其加载到您的测试数据库中,然后修改每个测试会话的搜索路径以在override
之前搜索pg_catalog
。提供了两个功能freeze_time
和unfreeze_time
来启用和禁用冻结时间。
这里有一个想法:使用
mock_now()
表创建您自己的 mock_dates
:
create table mock_dates (
id serial PRIMARY KEY,
mock_date timestamptz not null
);
create or replace function mock_now()
returns timestamptz
as $$
declare
RET timestamptz;
begin
-- Delete first added date and assign it to RET
delete from mock_dates where id in (
select id from mock_dates order by id asc limit 1
)
returning mock_dates.mock_date into RET;
-- If no deletion happened just return the current timestamp
if RET is null then
return now();
end if;
-- Otherwise return the mocked date
return RET;
end;
$$
language plpgsql;
并插入一些模拟日期
insert into mock_dates (mock_date) values ('2001-03-11 02:34:00'::timestamptz);
insert into mock_dates (mock_date) values ('2002-05-22 01:49:00'::timestamptz);
并使用
mock_now()
代替now()
。它会返回一次插入mock_dates
表的时间戳(先进先出)。
当表格为空时,它将像默认情况一样工作
now()
.
只需确保
mock_dates
表在生产中是空的😁
或者您甚至可以为生产定义一个不同的函数,它甚至不会尝试读取
mock_dates
表。
以下是创建模拟
now()
函数的方法:
override.now()
search_path
使Postgres在override
模式中查看(如何伪造函数)'test.freeze_time'
(如何声明变量让我们先定义函数:
set session test.freeze_time = '';
CREATE OR REPLACE FUNCTION override.now()
RETURNS timestamptz IMMUTABLE PARALLEL SAFE AS
$$
BEGIN
if current_setting('test.freeze_time') = '' then
return pg_catalog.now();
else
return current_setting('test.freeze_time')::timestamptz;
end if;
END
$$ language plpgsql;
现在这就是启用它的方式:
set search_path = override,pg_temp,"$user",public,pg_catalog;
set session test.freeze_time = '2023-03-03 00:00:00Z';
test=# SELECT now();
now
------------------------
2023-03-03 00:00:00+00
(1 row)
我们必须明确提及
pg_catalog
因为:
如果 pg_catalog 不在路径中,那么将在搜索任何路径项之前搜索它。
此外,不幸的是,我们不能使用
pg_temp
来定义我们的功能因为:
从不搜索函数或运算符名称。
老实说,DB 内部的东西总是很难从应用程序代码中测试。根据我的经验,最好的做法是只验证记录的状态。
因此,与其专门测试内部调用 now 函数,不如编写一个创建新记录的测试,然后验证记录是否已创建并修改字段集。那时它们应该彼此相等,并且可能在最后一两秒内,所以这就是您可以编写断言的所有内容。
然后您编写另一个测试来更改记录中的某些值,并编写断言修改的戳记与创建的戳记不同,更新时间更近,并且可能在最后一秒或两秒内。