在对 Express Node.js 应用程序进行单元测试时,如何模拟 Redis?

Pau*_*oyd 5 javascript unit-testing mocking redis node.js

我想对我的 Express 应用程序进行单元测试,其中部分内容从 Redis 数据库设置和获取数据。调查为什么我的一些测试间歇性失败 \xe2\x80\x94 并且随着我继续开发我的应用程序 \xe2\x80\x94 更频繁地失败,我意识到我的测试仍然调用 Redis 服务器,而不是我认为我嘲笑过的服务器记忆。

\n\n

如何成功模拟 Redis 客户端 (ioredis),以便可以相互隔离地对部分代码进行单元测试,而无需调用 Redis 服务器?

\n\n

这是我到目前为止所拥有的。

\n\n

待测代码

\n\n

我有一个应用程序模型,它将 package.json 中的值与 Redis 数据库中的值合并,通常带有后备值。可以添加或检索值。

\n\n
// models/application.js\n\nimport fs from \'fs\';\nimport {client} from \'../config/database.js\';\n\nexport const getAll = async () => {\n  const data = await client.hgetall(\'application\');\n  const package_ = JSON.parse(fs.readFileSync(\'package.json\'));\n\n  const application = {\n    name: data.name || \'IndieKit\',\n    version: package_.version,\n    description: package_.description,\n    repository: package_.repository,\n    locale: data.locale || \'en\',\n    themeColor: data.themeColor || \'#0000ee\'\n  };\n\n  return application;\n};\n\nexport const get = async key => {\n  const application = await getAll();\n  return application[key];\n};\n\nexport const setAll = async values => {\n  return client.hmset(\'application\', values);\n};\n\nexport const set = async (key, value) => {\n  return client.hset(\'application\', key, value);\n};\n
Run Code Online (Sandbox Code Playgroud)\n\n

client从一个单独的文件调用,我在其中使用ioredis连接到 Redis 服务器:

\n\n
// config/database.js\n\nimport Redis from \'ioredis\';\n\nexport const client = new Redis(process.env.REDIS_URL);\nexport const expires = 3600;\n\nclient.on(\'error\', error => {\n  console.error(\'Redis\', error);\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n

测试

\n\n

我\xe2\x80\x99m使用Ava作为我的测试框架,使用rewiremock用ioredis-mock \xe2\x80\xa6\xc2\xa0替换ioredis依赖,或者我想!

\n\n
// tests/models/application.js\n\nimport test from \'ava\';\nimport {rewiremock} from \'../helpers/rewiremock.js\';\nimport {client} from \'../../config/database.js\';\n\ntest.beforeEach(async t => {\n  t.context.applicationModel = await rewiremock.proxy(() => {\n    return import(\'../../models/application.js\');\n  });\n});\n\ntest.afterEach.always(() => {\n  client.flushall();\n});\n\ntest.serial(\'Gets a value\', async t => {\n  await t.context.applicationModel.set(\'name\', \'foobar1\');\n  const result = await t.context.applicationModel.get(\'name\');\n  t.is(result, \'foobar1\');\n});\n\ntest.serial(\'Gets all values\', async t => {\n  await t.context.applicationModel.set(\'name\', \'foobar2\');\n  const result = await t.context.applicationModel.getAll();\n  t.is(result.name, \'foobar2\');\n});\n\ntest.serial(\'Sets a value\', async t => {\n  await t.context.applicationModel.set(\'name\', \'foobar3\');\n  const result = await t.context.applicationModel.get(\'name\');\n  t.is(result, \'foobar3\');\n});\n\ntest.serial(\'Sets all values\', async t => {\n  await t.context.applicationModel.setAll({\n    name: \'foobar4\',\n    locale: \'bazqux1\'\n  });\n  const result = await t.context.applicationModel.getAll();\n  t.is(result.name, \'foobar4\');\n  t.is(result.locale, \'bazqux1\');\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在再看一遍,显然存在几个问题。首先,我在 上执行一条flushAll命令client,该命令直接与 Redis 服务器交互,而不是与内存中的模拟服务器交互。其次,需要为每个测试使用不同的值应该提醒我有什么问题;如果我在每次测试后真正刷新数据库,我就不需要这个了。

\n\n

这里\xe2\x80\x99s rewiremock helper I\xe2\x80\x99m 导入到这个测试中:

\n\n
import rewiremock from \'rewiremock/node.js\';\n\nrewiremock(\'ioredis\')\n  .by(\'ioredis-mock\');\n\nexport {rewiremock};\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以现在我发现自己很茫然;对模拟、存根和依赖注入(在这里和其他测试中)感到困惑和困惑。我究竟做错了什么?对此类功能进行单元测试的最佳方法是什么?

\n