我有一个Express应用程序,当某些端点被命中时,使用node-slack-sdk向Slack发帖。我正在尝试为一个路由编写集成测试,该路由在许多其他方面调用该库中的方法。
我想阻止Slack库中某些方法的所有默认行为,并简单地断言用某些参数调用这些方法。
我试图简化问题。如何在chat
的实例中存根一个方法(实际上嵌套在WebClient
内),阻止原始功能,并对其所调用的参数进行断言?
我已经尝试过很多无法解决的问题,所以我编辑了这个并提供了一个非常简化的设置:
index.html
const express = require('express');
const {WebClient} = require('@slack/client');
const app = express();
const web = new WebClient('token');
app.post('/', (req, res) => {
web.chat.postMessage({
text: 'Hello world!',
token: '123'
})
.then(() => {
res.json({});
})
.catch(err => {
res.sendStatus(500);
});
});
module.exports = app;
index.test.html
'use strict';
const app = require('../index');
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const expect = chai.expect;
chai.use(chaiHttp);
const {WebClient} = require('@slack/client');
describe('POST /', function() {
before(function() {
// replace WebClient with a simplified implementation, or replace the whole module.
});
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
// assert that web.chat.postMessage was called with {message: 'Hello world!'}, etc
});
});
});
有一些事情使这很困难,而不像其他例子。其一,我们无法访问测试中的web
实例,因此我们无法直接存根。二,该方法隐藏在chat
属性web.chat.postMessage
内,这也与我在sinon,proxyquire等文档中看到的其他示例不同。
答案 0 :(得分:2)
您的示例的设计不是非常可测试的,这就是您遇到这些问题的原因。为了使其更具可测性和连贯性,最好传入WebClient对象和其他依赖项,而不是在路由中创建它们。
const express = require('express');
const {WebClient} = require('@slack/client');
const app = express();//you should be passing this in as well. But for the sake of this example i'll leave it
module.exports = function(webClient) {
app.post('/', (req, res) => {
web.chat.postMessage({
text: 'Hello world!',
token: '123'
})
.then(() => {
res.json({});
})
.catch(err => {
res.sendStatus(500);
});
})
return app;
};
为了实现这一点,请在更高的模块中构建对象/路由。 (您可能需要编辑为您生成的快速文件。我不确定,我个人使用重构的快速版本来满足我的需求。)通过传入WebClient,您现在可以为测试创建存根。
'use strict';
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const expect = chai.expect;
chai.use(chaiHttp);
const {WebClient} = require('@slack/client');
const web = new WebClient('token');
let app = require('../index')(web);
describe('POST /', function() {
it('should call chat.update with specific arguments', function() {
const spy = sinon.spy();
sinon.stub(web.chat, 'postMessage').callsFake(spy);
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
assert(spy.calledWith({message: 'Hello world!'}));
});
});
});
这称为Dependency Injection。而不是让索引模块构建它的依赖关系,WebClient,您的更高模块将传递依赖关系,以便它们控制它的较低模块的状态。您的更高模块,您的测试,现在具有为下层模块创建存根所需的控制,索引。
上面的代码只是快速的工作。我没有经过测试,看它是否有效,但它应该回答你的问题。
答案 1 :(得分:1)
所以@Plee,在结构方面有一些好处。但我的答案更多的是关于手头的问题,如何使测试工作和你需要理解的事情。为了更好地编写单元测试,你应该使用其他好的资源,比如书籍和文章,我假设在线上会有很多很好的资源
你在测试中做错的第一件事是第一行本身
const app = require('../index');
执行此操作,您加载index
文件,然后执行以下代码
const {WebClient} = require('@slack/client');
const app = express();
const web = new WebClient('token');
现在模块已加载原始@slack/client
并创建了一个在模块外无法访问的对象。所以我们失去了定制/间谍/存根模块的机会。
所以第一个拇指规则
切勿在测试中全局加载此类模块。或者在存根之前永远不加载
接下来我们想要的是,在我们的测试中,我们应该加载我们想要存根的原始客户端库
'use strict';
const {WebClient} = require('@slack/client');
const sinon = require('sinon');
既然我们无法在index.js
中获取创建的对象,我们需要在创建对象时捕获它。这可以像下面这样完成
var current_client = null;
class MyWebClient extends WebClient {
constructor(token, options) {
super(token, options);
current_client = this;
}
}
require('@slack/client').WebClient = MyWebClient;
现在我们所做的是原始WebClient
被我们的MyWebClient
取代,当有人创建相同的对象时,我们只是在current_client
中捕获它。这假定只从我们加载的模块中创建一个对象。
接下来是更新我们的before
方法来存根web.chat.postMessage
方法。因此,我们更新了我们的before
方法,如下所示
before(function() {
current_client = null;
app = require('../index');
var stub = sinon.stub();
stub.resolves({});
current_client.chat.postMessage = stub;
});
现在出现了测试功能,我们将在下面更新
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
expect(current_client.chat.postMessage
.getCall(0).args[0]).to.deep.equal({
text: 'Hello world!',
token: '123'
});
});
});
,结果是积极的
以下是我使用的完整index.test.js
,index.js
未更改
'use strict';
const {WebClient} = require('@slack/client');
const sinon = require('sinon');
var current_client = null;
class MyWebClient extends WebClient {
constructor(token, options) {
super(token, options);
current_client = this;
}
}
require('@slack/client').WebClient = MyWebClient;
const chai = require('chai');
const chaiHttp = require('chai-http');
const expect = chai.expect;
chai.use(chaiHttp);
let app = null;
describe('POST /', function() {
before(function() {
current_client = null;
app = require('../index');
var stub = sinon.stub();
stub.resolves({});
current_client.chat.postMessage = stub;
});
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
expect(current_client.chat.postMessage
.getCall(0).args[0]).to.deep.equal({
text: 'Hello world!',
token: '123'
});
});
});
});
答案 2 :(得分:1)
基于其他评论,您似乎处于一个代码库中,制作一个激烈的重构会很困难。所以这是我如何在不对index.js
进行任何更改的情况下进行测试。
我在这里使用rewire库从索引文件中获取并存根web
变量。
'use strict';
const rewire = require('rewire');
const app = rewire('../index');
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const expect = chai.expect;
chai.use(chaiHttp);
const web = app.__get__('web');
describe('POST /', function() {
beforeEach(function() {
this.sandbox = sinon.sandbox.create();
this.sandbox.stub(web.chat);
});
afterEach(function() {
this.sandbox.restore();
});
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
const called = web.chat.postMessage.calledWith({message: 'Hello world!'});
expect(called).to.be.true;
});
});
});