微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

用 Sinon 模拟服务模块

如何解决用 Sinon 模拟服务模块

我已经进入了单元测试阶段,老实说,网上的所有不同示例都让我感到困惑。我对摩卡和柴很了解,但诗浓是另一回事。

所以我认为这是一个非常直接的设置。我有一个调用控制器的 POST 路由。这个控制器就是这样(去掉了一些基本的验证码)

const { createuser } = require('../services/user.service');

const apiResponse = require('../helpers/apiResponse');

const postUser = async (req,res) => {
    const user = {
      account_id: req.body.id,status: req.body.status,created_at: new Date(),updated_at: new Date(),};

    const result = await createuser(user);
    return apiResponse.successResponseWithData(res,'User added.',result.affectedRows);
  } catch (err) {
    return apiResponse.errorResponse(res,err);
  }
};

module.exports = {
  postUser,};

所以它真正做的就是验证,然后使用 req 创建一个用户对象并将其传递给服务类。这个服务类只是将数据传递给数据库类。

const { addUserToDb } = require('../database/user.db');

const createuser = async (user) => {
  try {
    const createdUser = await addUserToDb(user);
    return createdUser;
  } catch (err) {
    throw new Error(err);
  }
};

module.exports = {
  createuser,};

我不会展示数据库类,因为我首先要关注的是控制器,然后我可以自己完成剩下的工作。

所以据我所知,我应该测试函数。如果一个函数进行外部调用,我应该监视、模拟、存根该调用吗?如果依赖项之一,我应该只监视、模拟或存根此函数依赖项 有它自己的依赖(就像上面的服务模块有一个数据库调用依赖),这应该在另一个测试中执行吗?抱歉,我问了几个问题来帮助我理解。

无论如何,我已经创建了一个 user.controller.test.js 文件。我还没有走远,但这是我目前所拥有的

const chai = require('chai');
const sinon = require('sinon');

const { expect } = chai;
const faker = require('faker');

const controller = require('../controllers/user.controller');
const service = require('../services/user.service');

const flushPromises = () => new Promise(setImmediate);

describe('user.controller',() => {
  describe('postUser',() => {
    beforeEach(() => {
        //I see a lot of code use a beforeEach,what should I be doing here?
    });

    it('should create a user when account_id and status params are provided',async () => {
      const req = {
        body: { account_id: faker.datatype.uuid(),status: 'true' },};

      const stubValue = {
        id: faker.datatype.id(),account_id: faker.datatype.uuid(),status: 'true',created_at: faker.date.past(),updated_at: faker.date.past(),};
      
    });
  });
});

老实说,我完全不知道我应该在这里测试什么。根据我的理解,我需要模拟我认为的服务模块。

有人可以提供一些关于我应该在这个测试中做什么的见解吗?

非常感谢

更新

感谢您的详细回复,我已经设法让间谍工作,这是向前迈出的一步。所以我想对我的服务模块进行测试,createuser 方法

您可以看到我的 createuser 方法用户对象作为参数并将其传递给数据库模块,然后将其插入到数据库中,然后返回用户对象。

所以在测试我的服务类时,我需要模拟这个对我的数据库模块的调用

const chai = require('chai');
const sinon = require('sinon');

const { expect } = chai;
const faker = require('faker');

const service = require('../services/user.service');
const database = require('../database/user.db');

describe('user.service',() => {
  describe('createuser',() => {
    it('should create a user when user object is provided',async () => {
      const user = {
        id: faker.datatype.string(),};
      const expectedUser = {
        id: user.id,status: user.status,created_at: user.created_at,updated_at: user.updated_at,};

      const mockedDatabase = sinon.mock(database);

      mockedDatabase.expects('addUserToDb').once().withArgs(expectedUser);
      await service.createuser(user);

      mockedDatabase.verify();
      mockedDatabase.restore();
    });
  });
});

当我测试这个时,我似乎得到了这个响应,而且它似乎仍在将记录插入到我的数据库中。

ExpectationError: Expected addUserToDb({
  id: 'yX7AX\\J&gf',created_at: 2020-06-03T03:10:23.472Z,updated_at: 2020-05-24T14:44:14.749Z
},'[...]') once (never called)
      at Object.fail (node_modules\sinon\lib\sinon\mock-expectation.js:314:25)
  
  

你知道我做错了什么吗?

谢谢

解决方法

在我尝试之前,我想建议在任何地方删除 try/catch 块,我假设您在 Node 应用程序中使用 expressJs,为此,请查看 express-promise-router 作为使用该路由器(而不是默认路由器)将自动 catch 抛出的任何内容,您只需要专注于代码...

以你的例子为例,你会写:

const { addUserToDb } = require('../database/user.db');

const createUser = async (user) => addUserToDb(user);

module.exports = {
  createUser,};

const { createUser } = require('../services/user.service');

const apiResponse = require('../helpers/apiResponse');

const postUser = async (req,res) => {
    const { id: account_id,status } = res.body;
    const result = await createUser({ account_id,status }); // set the date in the fn
    return apiResponse.successResponseWithData(res,'User added.',result.affectedRows);
};

module.exports = {
  postUser,};

如果出现错误并且在路线上的某个地方抛出错误,您将在响应中收到一条带有错误的好消息

关于代码本身,似乎更清晰易读——请记住,代码是给人类的,机器甚至不关心你如何命名变量?


现在,关于测试......我确实倾向于将事情分成 3 部分

  • 单元测试:功能本身,单一的,如验证、帮助程序等
  • 集成测试:当您调用 API 端点时应该返回什么
  • GUI 测试(或端到端/e2e):在 GUI 存在时应用,暂时跳过此部分

所以在你的情况下,首先要确定的是你在测试什么......然后从小块(单元测试)开始,向上移动到确保所有块都粘在一起的块( e2e)

enter image description here

所以它真正做的就是验证,然后使用 req 创建一个用户对象并将其传递给服务类。这个服务类只是将数据传递给数据库类。

似乎是一个很好的开始方式,所以它“验证”......让我们测试我们的验证,让我们通过 null,undefined,string 当你想要的只是 {{1 }} 以此类推,直到我们得到一个很好的想法,无论它通过什么,我们都会正确地回复有错误和没有错误

注意我倾向于使用 OpenAPI 规范,这对我来说更容易,因为它提供了两件事

是的,我总是测试一些验证,以确保它按预期工作,即使我 100% 信任该工具 ?

所以据我所知,我应该测试函数。

嗯,一个应用程序是一组功能,所以一切都很好?

如果一个函数进行外部调用,我应该监视、模拟、存根该调用吗?

我会尽我所能解释什么是诗浓的间谍、存根和嘲笑,请温柔?

间谍

它们告诉我们有关函数调用的信息,例如调用次数、参数、返回值等 - 它们有两种类型,匿名间谍或在我们的代码中包装方法的间谍

int
function testMyCallback(callback) { callback(); }

describe('testMyCallback fn',function() {
  it('should call the callback',function() {
    const callbackSpy = sinon.spy(); // anonymous spy - no arguments
    testMyCallback(callbackSpy);
    expect(callbackSpy).to.have.been.calledOnce;
  });
});

存根

是强大的间谍,因为它们具有间谍的所有功能,但它们替换目标函数,它们具有可以返回特定值或抛出特定异常的方法以及更多

它们非常适合用于解决有关外部调用的问题,因为它们取代了调用(因此您可以模拟调用行为,而不要使用原始调用)

最简单的例子是:

const user = {
  setNname: function(name) {
    this.name = name;
  }
}

describe('setname fn',function() {
  it('should be called with name',function() {
    const setNameSpy = sinon.spy(user,'setName'); // wrap method spy
    user.setName('Katie');
    expect(setNameSpy).to.have.been.calledOnce;
    expect(setNameSpy).to.have.been.valledWith('Katie');

    setNameSpy.restore(); // to remove the Spy and prevent future errors
  });
});

我们已经对我们的函数进行了 STUB 处理,并明确表示它是一个返回字符串 function isAdult(age) { return age > 21; } describe('Sinon Stub Example',() => { it('should pass',(done) => { const isAdult = sinon.stub().returns('something'); isAdult(0).should.eql('something'); isAdult(0).should.not.eql(false); done(); }); }); 的“函数”......现在,我们将永远不需要去函数本身,因为我们有 STUB 它,我们已经用我们自己的行为替换了真实的行为

在集成测试中调用 API 应用程序时使用 STUB 的另一个示例

something

您也可以存根 axios,但是您需要一个新的库,moxiosproxyquire 或更多...

模拟

有点类似于 Stubs(我们的 Power-Spies),但它们可用于替换整个对象并改变它们的行为,它们主要用于当您需要从单个对象中存根多个函数时 - 如果您愿意需要的是替换单个函数,存根更容易使用

Mock 会使事情变得过于简单,你甚至可能在不知情的情况下破坏你的应用程序,所以要注意......

通常使用的是,例如

describe('when we stub our API call',() => {
  beforeEach(() => {
    this.get = sinon.stub(request,'get'); // stub "request.get" function
  });

  afterEach(() => {
    request.get.restore(); // remove our power-spy
  });

  describe('GET /api/v1/accounts',() => {

    const responseObject = {
      status: 200,headers: {
        'content-type': 'application/json'
      }
    };
    const responseBody = {
      status: 'success',data: [
        {
          accountId: 1,status: 'active'
        },{
          accountId: 2,status: 'disabled'
        }
      ]
    };

    it('should return all accounts',(done) => {
      // the 3 objects of our callback (err,res,body)
      this.get.yields(null,responseObject,JSON.stringify(responseBody));

      request.get(`${base}/api/v1/movies`,(err,body) => {
        expect(res.statusCode).to.be.eql(200);
        expect(res.headers['content-type']).to.contain('application/json');

        body = JSON.parse(body);
        expect(body).to.be.an('array').that.includes(2);
        done();
      });
    });
  });
});

我们会一直忘记的是 function setupNewAccount(info,callback) { const account = { account_id: info.id,status: info.status,created_at: new Date(),updated_at: new Date() }; try { Database.save(account,callback); } catch (err) { callback(err); } } describe('setupNewAccount',function() { it('',function() { const account = { account_id: 1,status: 'active' }; const expectedAccount = { account_id: account.id,status: account.status }; const database = sinon.mock(Database); database.expectes('save').once().withArgs(expectedAccount); setupNewAccount(account,function() {}); database.verify(); database.restore(); }); }); 部分,为此,有一个名为 sinon-test 的包(还有一个...),它将在测试结束时自动清理

>

我只是希望它对你的一些问题有所帮助,现在更清楚了?

顺便说一句,对于截断 HTTP 请求,我使用 nock 因为我认为它比 Sinon 更容易阅读和使用,尤其是对于第一次阅读代码并且没有任何 Sinon 或 Nock 经验的人...

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?