实例的存根方法
我有一个Express应用程序,它使用node-slack-sdk在某些终端点击时将帖子发布到Slack。 我正在尝试为一条路线编写集成测试,其中包括从该库中调用一个方法。
我想阻止Slack库中某些方法的所有默认行为,并简单地声明这些方法是用某些参数调用的。
我试图简化这个问题。 我如何将WebClient
实例的方法(实际上嵌套在chat
)进行存根,阻止原始功能,并对其调用的参数进行断言?
我尝试了很多没有奏效的东西,所以我正在编辑它,并在这里提供了一个极其简化的设置:
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等文档中看到的其他示例不同。
你的例子的设计不是很好测试,这就是为什么你有这些问题。 为了使其更具可测试性和内聚性,最好传入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!'}));
});
});
});
这就是所谓的依赖注入。 您的高级模块不会让您的索引模块构建它的依赖性WebClient,而是会传递依赖关系,以便它们控制低级模块的状态。 你的高级模块,你的测试,现在有了它需要的控制来为下层模块索引创建一个存根。
上面的代码只是快速的工作。 我没有测试过它是否有效,但它应该回答你的问题。
所以@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'
});
});
});
});
根据其他意见,看起来你是在一个代码库中进行剧烈的重构会很困难。 所以这里是我将如何测试而不对你的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;
});
});
});
链接地址: http://www.djcxy.com/p/82257.html
下一篇: Should I hide DTOs and View Models behind interfaces or abstractions?