模拟 Postgresql now() 函数进行测试

问题描述 投票:0回答:4

我有以下堆栈

  • Node/Express 后端
  • Postgresql 10 数据库
  • 用于测试的摩卡咖啡
  • Sinon 的嘲讽

我写了一堆端到端测试来测试我所有的网络服务。问题是其中一些是时间相关的(如“给我最后 X 秒的修改记录”)。

sinon
非常擅长模拟 Node 中所有与时间/日期相关的内容,但是我的 Postgresql 表中有一个
modified
字段,该字段填充了触发器:

CREATE FUNCTION update_modified_column()
  RETURNS TRIGGER AS $$
BEGIN
  NEW.modified = now();
  RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';

问题当然是

sinon
不能覆盖那个
now()
函数。

关于如何解决这个问题的任何想法?问题不在于在测试开始时设置特定日期,而是比实时提前时间更快(在我的一项测试中,我想更改数据库中的一些内容,将“当前时间”提前一天,更改数据库中的更多内容并进行网络服务调用以查看结果)。

我自己可以想出一些解决方案,但它们都涉及更改应用程序代码并使其变得不那么优雅。我认为您的应用程序代码不应受到您想要测试它这一事实的影响。

node.js postgresql mocha.js sinon
4个回答
2
投票

我发现this very neat gist它提供了一个伪造的

NOW()
函数,它存在于一个单独的模式中。您将其加载到您的测试数据库中,然后修改每个测试会话的搜索路径以在
override
之前搜索
pg_catalog
。提供了两个功能
freeze_time
unfreeze_time
来启用和禁用冻结时间。


1
投票

这里有一个想法:使用

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
表。


0
投票

以下是创建模拟

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
来定义我们的功能因为

从不搜索函数或运算符名称。


-2
投票

老实说,DB 内部的东西总是很难从应用程序代码中测试。根据我的经验,最好的做法是只验证记录的状态。

因此,与其专门测试内部调用 now 函数,不如编写一个创建新记录的测试,然后验证记录是否已创建并修改字段集。那时它们应该彼此相等,并且可能在最后一两秒内,所以这就是您可以编写断言的所有内容。

然后您编写另一个测试来更改记录中的某些值,并编写断言修改的戳记与创建的戳记不同,更新时间更近,并且可能在最后一秒或两秒内。

© www.soinside.com 2019 - 2024. All rights reserved.