使用 mocha 和 chai 对 nodejs 模块进行单元测试
Unit testing nodejs module using mocha and chai
我有一个正在为其编写单元测试的 nodejs 应用程序,我遇到以下情况:我有一个 config.js 文件,我在其中读取多个参数作为环境变量和一些连接到 RabbitMQ 的证书.我将此配置导出为模块。
我想做的是创建一个单元测试文件,在不同的场景下测试模块,并尝试达到非常高的覆盖率。
我有几个环境,例如 LOCAL_DEV、DEV、UAT 和 PROD,以及多个 RabbitMQ 实例。例如,对于开发人员,我使用密钥文件连接到 RabbitMQ,在 LOCAL_DEV 中仅使用用户名和密码,在 UAT 中使用 pfx 等等。
像 process.env.ENO_ENV 这样的环境变量在部署过程中被注入,我通过 .env_dev 或 .env_uat 文件获得的其他配置参数。我在进行部署时还注入了证书,并且它们的路径在“.env_”文件中给出。
例如,在 DEV 中,我有 process.env.ENO_ENV = dev 和 .env_dev 看起来像:
RABBITMQ_SSL_KEYFILE = 'tls/dev/rmq-XXX.key'
RABBITMQ_SSL_CERTFILE = 'tls/dev/rmq-XXX.crt'
RABBITMQ_SSL_CAFILE = 'tls/dev/rmq-dev-ca.crt'
测试是在 Gitlab 中完成的,我没有给出任何证书文件或 .env_ 文件,所以我需要模拟 process.env.ENO_ENV 和 .env_file.
config.js 文件的内容:
const fs = require('fs');
const path = require('path');
// Load config for the set env
global.appRoot = path.resolve(__dirname);
const eno_env = process.env.ENO_ENV || "";
if (process.env.ENO_ENV == 'dev' ||
process.env.ENO_ENV == 'local_dev' ||
process.env.ENO_ENV == 'uat' ||
process.env.ENO_ENV == 'prod') {
require('dotenv').config({ path: path.join(global.appRoot, '.env_' + eno_env) });
} else {
require('dotenv').config({ path: path.join(global.appRoot, '.env') });
}
const {
PORT,
RABBITMQ_PROTOCOL,
RABBITMQ_USER,
RABBITMQ_PASS,
RABBITMQ_HOSTS,
RABBITMQ_PORT,
RABBITMQ_VHOST,
RABBITMQ_EXCHANGE,
RABBITMQ_QUEUE,
RABBITMQ_SSL_CAFILE,
RABBITMQ_SSL_KEYFILE,
RABBITMQ_SSL_CERTFILE,
RABBITMQ_SSL_PFX,
RABBITMQ_SSL_PASSWORD,
} = process.env;
let opts = {};
if (RABBITMQ_SSL_PFX) {
console.log ('RABBITMQ_SSL_PFX : ' + path.join(global.appRoot, RABBITMQ_SSL_PFX));
opts = {
pfx: fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_PFX)),
passphrase: RABBITMQ_SSL_PASSWORD,
ca: [fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_CAFILE))],
rejectUnauthorized: false,
};
} else if (RABBITMQ_SSL_KEYFILE) {
opts = {
key: fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_KEYFILE)),
cert: fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_CERTFILE)),
ca: [fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_CAFILE))],
rejectUnauthorized: false,
};
}
const config = {
app: {
port: parseInt(PORT) || 8126,
enoenv: eno_env,
},
rabbitmq: {
protocol: RABBITMQ_PROTOCOL || 'amqps',
user: RABBITMQ_USER || 'test',
pass: RABBITMQ_PASS || 'test',
hosts: RABBITMQ_HOSTS || 'localhost',
port: parseInt(RABBITMQ_PORT) || 5671,
vhost: RABBITMQ_VHOST || 'test_virtualhost',
exchange: RABBITMQ_EXCHANGE || 'test_exchange',
queue: RABBITMQ_QUEUE || 'test_queue',
opts,
},
};
module.exports = config;
我的问题是通过使用名为 configCert1UnitTest.js 的文件实现 mocha 和 chai 的单元测试覆盖率:
const should = require('chai').should();
process.env['RABBITMQ_SSL_PFX'] = '/test/test.json';
process.env['RABBITMQ_SSL_PASSWORD'] = 'test';
process.env['RABBITMQ_SSL_CAFILE'] = '/test/test.json';
const config = require('./../config');
describe('env 1', function () {
// running tests
it('reads env RABBITMQ_SSL_PFX property, config.rabbitmq.opts.pfx and test.json file correctly', function () {
try {
//target = fs.readFileSync(path.join(global.appRoot, process.env['RABBITMQ_SSL_PFX']));
should.equal(
config.rabbitmq.opts.pfx.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error("error reading env RABBITMQ_SSL_PFX property, config.rabbitmq.opts.pfx or test.json: " + e.message);
}
});
it('reads env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca and test.json file correctly', function () {
try {
should.equal(
config.rabbitmq.opts.ca.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error("error reading env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca or test.json: " + e.message);
}
});
});
delete process.env.RABBITMQ_SSL_PFX;
delete process.env.RABBITMQ_SSL_PASSWORD;
delete process.env.RABBITMQ_SSL_CAFILE;
现在,为了覆盖 condif.js 中的另一个 IF 的情况,我在文件 configCert2UnitTest.js 中编写了第二个测试用例:
const should = require('chai').should();
process.env['RABBITMQ_SSL_KEYFILE'] = '/test/test.json';
process.env['RABBITMQ_SSL_CERTFILE'] = '/test/test.json';
process.env['RABBITMQ_SSL_CAFILE'] = '/test/test.json';
const config = require('./../config');
describe('env 2', function () {
// running tests
it('reads env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key and test.json file correctly', function () {
try {
should.equal(
config.rabbitmq.opts.key.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error("error reading env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key or test.json: " + e.message);
}
});
it('reads env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert and test.json file correctly', function () {
try {
should.equal(
config.rabbitmq.opts.cert.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error('error reading env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert or test.json:' + e.message);
}
});
it('reads env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca and test.json file correctly', function () {
try {
should.equal(
config.rabbitmq.opts.ca.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error('error reading env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca or test.json: ' + e.message);
}
});
});
delete process.env.RABBITMQ_SSL_KEYFILE;
delete process.env.RABBITMQ_SSL_CERTFILE;
delete process.env.RABBITMQ_SSL_CAFILE;
问题是测试没有覆盖模块配置的 ELSE。
我的测试命令 package.json:
...
"test": "npx nyc mocha test --exit --timeout 10000 --reporter mocha-junit-reporter",
...
输出:
PS D:\zzz-api> npx mocha test
global.appRoot : D:\zzz-liveprice-api
RABBITMQ_SSL_PFX : D:\zzz-api\test\test.json
env 1
✔ reads env RABBITMQ_SSL_PFX property, config.rabbitmq.opts.pfx and test.json file correctly
✔ reads env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca and test.json file correctly
env 2
1) reads env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key and test.json file correctly
2) reads env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert and test.json file correctly
✔ reads env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca and test.json file correctly
Dummy Test
✔ First test that should pass
Bindings Unit Test
✔ Should return true if the correct bindings are found for the EUA
✔ Should return true if the correct bindings are found for the POWER
✔ Should return true if the correct bindings are found for the GAS
7 passing (534ms)
2 failing
1) env 2
reads env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key and test.json file correctly:
Error: error reading env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key or test.json: Cannot read property 'toString' of undefined
at Context.<anonymous> (test\configCert2UnitTest.js:19:19)
at processImmediate (internal/timers.js:439:21)
2) env 2
reads env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert and test.json file correctly:
Error: error reading env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert or test.json:Cannot read property 'toString' of undefined
at Context.<anonymous> (test\configCert2UnitTest.js:30:19)
at processImmediate (internal/timers.js:439:21)
我忘了提到我在 \test 目录中为测试设置了一个 test.json 文件,其中包含 '{\r\n "属性" : "test"\r\n}' 只是为了模拟证书,在 Gitlab 中测试时缺少这些证书。
有什么办法解决这个问题吗?谢谢!
好的,我通过执行以下操作设法解决了我的问题:
我在 config.js 中创建了一个函数:
const fs = require('fs');
const path = require('path');
module.exports = () => {
// Load config for the set env
global.appRoot = path.resolve(__dirname);
const eno_env = process.env.ENO_ENV || "";
...
return config;
}
然后,在我的测试文件 configUnitTest.js
中,我将常量定义移到 describe()
方法中,并将其放在 before()
块中,而不是在测试开始时使用它文件。
此外,mocha 会测试文件夹 test
中的其余文件,如果有另一个测试文件显示为 const conf = require('../config')();
,这将
const should = require('chai').should();
const mock = require('mock-fs');
const path = require('path');
describe('Reading config.js - environmental vars 1', function () {
let conf;
before('mocking files and process.env vars', function () {
...
process.env['ENO_ENV'] = 'local_dev';
conf = require('../config')(); // reads process.env.ENO_ENV
});
...
// running tests
it('TRUE if it reads var process.env.RABBITMQ_SSL_PFX and mocked file test.json', function () {
...
});
...
});
describe('Reading config.js - environmental vars 2', function () {
let conf;
before('mocking files and process.env vars', function () {
...
conf = require('../config')(); // reads const eno_env = process.env.ENO_ENV || "";
});
...
// running tests
it('TRUE if it reads var process.env.RABBITMQ_SSL_PFX and mocked file test.json', function () {
...
});
...
});
希望对大家有所帮助。
我有一个正在为其编写单元测试的 nodejs 应用程序,我遇到以下情况:我有一个 config.js 文件,我在其中读取多个参数作为环境变量和一些连接到 RabbitMQ 的证书.我将此配置导出为模块。
我想做的是创建一个单元测试文件,在不同的场景下测试模块,并尝试达到非常高的覆盖率。
我有几个环境,例如 LOCAL_DEV、DEV、UAT 和 PROD,以及多个 RabbitMQ 实例。例如,对于开发人员,我使用密钥文件连接到 RabbitMQ,在 LOCAL_DEV 中仅使用用户名和密码,在 UAT 中使用 pfx 等等。
像 process.env.ENO_ENV 这样的环境变量在部署过程中被注入,我通过 .env_dev 或 .env_uat 文件获得的其他配置参数。我在进行部署时还注入了证书,并且它们的路径在“.env_”文件中给出。
例如,在 DEV 中,我有 process.env.ENO_ENV = dev 和 .env_dev 看起来像:
RABBITMQ_SSL_KEYFILE = 'tls/dev/rmq-XXX.key'
RABBITMQ_SSL_CERTFILE = 'tls/dev/rmq-XXX.crt'
RABBITMQ_SSL_CAFILE = 'tls/dev/rmq-dev-ca.crt'
测试是在 Gitlab 中完成的,我没有给出任何证书文件或 .env_ 文件,所以我需要模拟 process.env.ENO_ENV 和 .env_file.
config.js 文件的内容:
const fs = require('fs');
const path = require('path');
// Load config for the set env
global.appRoot = path.resolve(__dirname);
const eno_env = process.env.ENO_ENV || "";
if (process.env.ENO_ENV == 'dev' ||
process.env.ENO_ENV == 'local_dev' ||
process.env.ENO_ENV == 'uat' ||
process.env.ENO_ENV == 'prod') {
require('dotenv').config({ path: path.join(global.appRoot, '.env_' + eno_env) });
} else {
require('dotenv').config({ path: path.join(global.appRoot, '.env') });
}
const {
PORT,
RABBITMQ_PROTOCOL,
RABBITMQ_USER,
RABBITMQ_PASS,
RABBITMQ_HOSTS,
RABBITMQ_PORT,
RABBITMQ_VHOST,
RABBITMQ_EXCHANGE,
RABBITMQ_QUEUE,
RABBITMQ_SSL_CAFILE,
RABBITMQ_SSL_KEYFILE,
RABBITMQ_SSL_CERTFILE,
RABBITMQ_SSL_PFX,
RABBITMQ_SSL_PASSWORD,
} = process.env;
let opts = {};
if (RABBITMQ_SSL_PFX) {
console.log ('RABBITMQ_SSL_PFX : ' + path.join(global.appRoot, RABBITMQ_SSL_PFX));
opts = {
pfx: fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_PFX)),
passphrase: RABBITMQ_SSL_PASSWORD,
ca: [fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_CAFILE))],
rejectUnauthorized: false,
};
} else if (RABBITMQ_SSL_KEYFILE) {
opts = {
key: fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_KEYFILE)),
cert: fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_CERTFILE)),
ca: [fs.readFileSync(path.join(global.appRoot, RABBITMQ_SSL_CAFILE))],
rejectUnauthorized: false,
};
}
const config = {
app: {
port: parseInt(PORT) || 8126,
enoenv: eno_env,
},
rabbitmq: {
protocol: RABBITMQ_PROTOCOL || 'amqps',
user: RABBITMQ_USER || 'test',
pass: RABBITMQ_PASS || 'test',
hosts: RABBITMQ_HOSTS || 'localhost',
port: parseInt(RABBITMQ_PORT) || 5671,
vhost: RABBITMQ_VHOST || 'test_virtualhost',
exchange: RABBITMQ_EXCHANGE || 'test_exchange',
queue: RABBITMQ_QUEUE || 'test_queue',
opts,
},
};
module.exports = config;
我的问题是通过使用名为 configCert1UnitTest.js 的文件实现 mocha 和 chai 的单元测试覆盖率:
const should = require('chai').should();
process.env['RABBITMQ_SSL_PFX'] = '/test/test.json';
process.env['RABBITMQ_SSL_PASSWORD'] = 'test';
process.env['RABBITMQ_SSL_CAFILE'] = '/test/test.json';
const config = require('./../config');
describe('env 1', function () {
// running tests
it('reads env RABBITMQ_SSL_PFX property, config.rabbitmq.opts.pfx and test.json file correctly', function () {
try {
//target = fs.readFileSync(path.join(global.appRoot, process.env['RABBITMQ_SSL_PFX']));
should.equal(
config.rabbitmq.opts.pfx.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error("error reading env RABBITMQ_SSL_PFX property, config.rabbitmq.opts.pfx or test.json: " + e.message);
}
});
it('reads env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca and test.json file correctly', function () {
try {
should.equal(
config.rabbitmq.opts.ca.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error("error reading env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca or test.json: " + e.message);
}
});
});
delete process.env.RABBITMQ_SSL_PFX;
delete process.env.RABBITMQ_SSL_PASSWORD;
delete process.env.RABBITMQ_SSL_CAFILE;
现在,为了覆盖 condif.js 中的另一个 IF 的情况,我在文件 configCert2UnitTest.js 中编写了第二个测试用例:
const should = require('chai').should();
process.env['RABBITMQ_SSL_KEYFILE'] = '/test/test.json';
process.env['RABBITMQ_SSL_CERTFILE'] = '/test/test.json';
process.env['RABBITMQ_SSL_CAFILE'] = '/test/test.json';
const config = require('./../config');
describe('env 2', function () {
// running tests
it('reads env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key and test.json file correctly', function () {
try {
should.equal(
config.rabbitmq.opts.key.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error("error reading env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key or test.json: " + e.message);
}
});
it('reads env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert and test.json file correctly', function () {
try {
should.equal(
config.rabbitmq.opts.cert.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error('error reading env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert or test.json:' + e.message);
}
});
it('reads env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca and test.json file correctly', function () {
try {
should.equal(
config.rabbitmq.opts.ca.toString('utf8'),
'{\r\n "property" : "test"\r\n}');
} catch(e) {
throw new Error('error reading env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca or test.json: ' + e.message);
}
});
});
delete process.env.RABBITMQ_SSL_KEYFILE;
delete process.env.RABBITMQ_SSL_CERTFILE;
delete process.env.RABBITMQ_SSL_CAFILE;
问题是测试没有覆盖模块配置的 ELSE。
我的测试命令 package.json:
...
"test": "npx nyc mocha test --exit --timeout 10000 --reporter mocha-junit-reporter",
...
输出:
PS D:\zzz-api> npx mocha test
global.appRoot : D:\zzz-liveprice-api
RABBITMQ_SSL_PFX : D:\zzz-api\test\test.json
env 1
✔ reads env RABBITMQ_SSL_PFX property, config.rabbitmq.opts.pfx and test.json file correctly
✔ reads env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca and test.json file correctly
env 2
1) reads env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key and test.json file correctly
2) reads env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert and test.json file correctly
✔ reads env RABBITMQ_SSL_CAFILE property, config.rabbitmq.opts.ca and test.json file correctly
Dummy Test
✔ First test that should pass
Bindings Unit Test
✔ Should return true if the correct bindings are found for the EUA
✔ Should return true if the correct bindings are found for the POWER
✔ Should return true if the correct bindings are found for the GAS
7 passing (534ms)
2 failing
1) env 2
reads env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key and test.json file correctly:
Error: error reading env RABBITMQ_SSL_KEYFILE property, config.rabbitmq.opts.key or test.json: Cannot read property 'toString' of undefined
at Context.<anonymous> (test\configCert2UnitTest.js:19:19)
at processImmediate (internal/timers.js:439:21)
2) env 2
reads env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert and test.json file correctly:
Error: error reading env RABBITMQ_SSL_CERTFILE property, config.rabbitmq.opts.cert or test.json:Cannot read property 'toString' of undefined
at Context.<anonymous> (test\configCert2UnitTest.js:30:19)
at processImmediate (internal/timers.js:439:21)
我忘了提到我在 \test 目录中为测试设置了一个 test.json 文件,其中包含 '{\r\n "属性" : "test"\r\n}' 只是为了模拟证书,在 Gitlab 中测试时缺少这些证书。
有什么办法解决这个问题吗?谢谢!
好的,我通过执行以下操作设法解决了我的问题:
我在 config.js 中创建了一个函数:
const fs = require('fs');
const path = require('path');
module.exports = () => {
// Load config for the set env
global.appRoot = path.resolve(__dirname);
const eno_env = process.env.ENO_ENV || "";
...
return config;
}
然后,在我的测试文件 configUnitTest.js
中,我将常量定义移到 describe()
方法中,并将其放在 before()
块中,而不是在测试开始时使用它文件。
此外,mocha 会测试文件夹 test
中的其余文件,如果有另一个测试文件显示为 const conf = require('../config')();
,这将
const should = require('chai').should();
const mock = require('mock-fs');
const path = require('path');
describe('Reading config.js - environmental vars 1', function () {
let conf;
before('mocking files and process.env vars', function () {
...
process.env['ENO_ENV'] = 'local_dev';
conf = require('../config')(); // reads process.env.ENO_ENV
});
...
// running tests
it('TRUE if it reads var process.env.RABBITMQ_SSL_PFX and mocked file test.json', function () {
...
});
...
});
describe('Reading config.js - environmental vars 2', function () {
let conf;
before('mocking files and process.env vars', function () {
...
conf = require('../config')(); // reads const eno_env = process.env.ENO_ENV || "";
});
...
// running tests
it('TRUE if it reads var process.env.RABBITMQ_SSL_PFX and mocked file test.json', function () {
...
});
...
});
希望对大家有所帮助。