实例化单个对象,同时保持可测试性的模式

时间:2019-01-12 19:54:29

标签: javascript

应用程序启动时,它将实例化Redis客户端对象:

const redisClient = redis.createClient(REDIS_URL);

我维护一个代理对象myRedisClient,该对象公开了客户端对象公开的API的承诺版本:

const get = (key) => 
    new Promise((resolve, reject) =>
        redisClient.get(key, (err, resp) => 
            err ? reject(err) : resolve(resp))

const myRedisClient = { get };

简单地隐式实例化redisClient感觉是错误的,因此我想将其放在显式调用的方法中:

let redisClient = null;
const init = () => redisClient || redis.createClient(REDIS_URL);
const myRedisClient = { init, get};

get需要访问redisClient。如果getredisClient变量位于同一文件中,则很简单。

但是,如果get在单独的文件中,则可以通过关闭redisClient并将redisClient作为get的自变量来使其可用。

// my-redis-client.js
import get from './get';

let redisClient = null;
const init = () => redisClient || redis.createClient(REDIS_URL);
const myRedisClient = { init, get(key) => get(redisClient, key) };

// get.js
const get = (redisClient, key) => 
    new Promise((resolve, reject) =>
        redisClient.get(key, (err, resp) => 
            err ? reject(err) : resolve(resp));

最后,我希望能够存根redisClient,并且我想然后可以从redis.createClient存根createCheckboxes = () => { return this.availableSizes.map((size) => { return < Size size={size}/> }); } (例如,使用测试运行器中内置的存根功能)。测试。

这是否听起来很理智?

1 个答案:

答案 0 :(得分:1)

  

仅隐式实例化redisClient感觉不对

不,这没什么问题,这正是您的应用在启动时应该做的事情。

redisClient的问题在于,它是一个有状态的全局变量,而您的myRedisClient 隐含地依赖。将redisClient的初始化放在要显式调用的方法中并不能解决这些问题。

要能够存根redisClient

  • 通过在启动时安装global.redisClient = …来明确指出它是一个全局变量。对于模拟,您可以轻松地覆盖全局(如果您不想使用独立的模拟并行运行多个测试)。
  • 要明确有关依赖项和
    • 使用其中myRedisClient.js依赖于redisClient.js模块的模块系统,该模块导出“全局”静态值,但让您的测试系统使用带有依赖项注入的模块加载器
    • 为您的myRedisClient创建一个构造函数或工厂函数,以便您可以通过将redisClient显式传递给它来对其进行初始化。

最后一个解决方案似乎符合您尝试做的事情。它应该看起来像这样:

// my-redis-client.js
default export function init(redisClient) {
    const get = util.promisify(redisClient.get).bind(redisClient);

    return { get };
}
// main.js
import initMyClient from 'my-redis-client'
const myRedisClient = initMyClient(redis.createClient(REDIS_URL));