【问题标题】:Creating stub for sequeilze models with association为具有关联的序列模型创建存根
【发布时间】:2020-03-09 06:46:26
【问题描述】:

我正在使用 mochachai 为 RESTful API 编写测试
我读过一些文章,人们建议为查询创建存根,而您实际上不应该进行数据库查询。
但是我如何确定它是否有效?
请参阅下面的控制器。

const Op = require('sequelize').Op
//Models
const {
    Item,
    Location,
    Combo,
    Service,
    ComboItem,
    ItemLocation
} = require('../models')

const _ = require('lodash')
//Services
const paginate = require('../services/PaginationService')







const getAllItems = async function(req, res) {
    if(req.query.location_id){
        let items
        const item = await Location.findOne({
            where: {
                id: 1
            },
            include: {
                model: Item,
                through: {
                    model: ItemLocation,
                    attributes: []
                },
                as: 'itemsAtLocation',
                include: [
                    {
                        model: Service,
                        as: 'service',
                        attributes: ["id"]

                    }, 
                    {
                        model: Combo,
                        as: 'combo',
                        attributes: ["start_date", "expiry_date"]
                    }
                ]
            }
        })
        if(!item)
            return res.status(200).send({
                status: true,
                message: "No item found at location!",
                data: {}
            })

        items = item.itemsAtLocation
        let data = {}
        data.services = []
        data.combos   = []
        _.forEach(items, item => {
            let itemData = {
                id: item.id,
                name: item.name,
                price: item.price,
                discount_per: item.discount_per,
            }
            if(item.service) 
                data.services.push(itemData)
            if(item.combo) {
                itemData.start_date = item.combo.start_date
                itemData.expiry_date = item.combo.expiry_date
                data.combos.push(itemData)
            }     
        })
        return res.status(200).send({
            status: true,
            message: "Successfully fetch all items!",
            data: data
        })
    } else {
        const items = await Item.findAll({
            include: [
                {
                    model: Service,
                    as: 'service',
                    attributes: ["id"]

                }, 
                {
                    model: Combo,
                    as: 'combo',
                    attributes: ["start_date", "expiry_date"]
                }
            ],
            attributes: ["id", "name", "price", "discount_per", "description"],
            ...paginate(+req.query.page, +req.query.per_page)
        })
        let data = {}
        data.services = []
        data.combos   = []
        _.forEach(items, item => {
            let itemData = {
                id: item.id,
                name: item.name,
                price: item.price,
                discount_per: item.discount_per,
            }
            if(item.service) 
                data.services.push(itemData)
            if(item.combo) {
                itemData.start_date = item.combo.start_date
                itemData.expiry_date = item.combo.expiry_date
                data.combos.push(itemData)
            }     
        })
        return res.status(200).send({
            status: true,
            message: "Successfully fetch all items!",
            data: data
        })
    }

}

module.exports = {
    getAllItems
}

从上面的代码可以看出。我需要queries 以特定形式返回数据。如果不是那种形式,事情就不会起作用。

有人可以建议我如何为这种函数创建存根,以便保留结构吗?

以下是我编写的测试,但它使用实际的数据库调用。

describe('GET /api/v1/items', function () {
    it('should fetch all items orgianized by their type', async () => {
        const result = await request(app)
            .get('/api/v1/items')
            .set('Accept', 'application/json')
            .expect('Content-Type', /json/)
            .expect(200)
        expect(result)
            .to.be.a('Object')
        expect(result.body.status)
            .to.be.a('Boolean').true
        expect(result.body.data, "data should be an Object and every key should an Array")
            .to.satisfy(data => {
                expect(data).to.be.a('Object')
                .to.not.be.null
                if(!_.isEmpty(data)) {
                    expect(data).to.have.any.keys('services', 'combos')  
                    _.forOwn(data, (value, key) => {
                        expect(data[key]).to.be.a('Array')
                     })
                    return true
                }
                return true
            })   
    })
})

【问题讨论】:

    标签: node.js sequelize.js mocha.js chai sinon


    【解决方案1】:

    您可以做到这一点的一种方法是对模型中的方法进行存根,即Location.findOneItem.findAll。所以你的测试可能看起来有点像下面的代码:

      const sinon = require('sinon');
      const Location = require('../models/location'); // Get your location model
      const Item = require('../models/item'); // Get your item model
    
      describe('myTest', () => {
        let findOneLocationStub;
        let findAllItemsStub;
    
        beforeEach(() => {
          findOneLocationStub = sinon.stub(Location, 'findOne');
          findAllItemsStub = sinon.stub(Item, 'findAll');
        });
    
        afterEach(() => {
          findOneLocationStub.verifyAndRestore();
          findAllItemsStub.verifyAndRestore();
        });
    
        it('returns 200 when location not found', () => {
          findOneLocationStub.resolves(null);
    
          expects...
        });
      });
    

    我没有运行测试,但类似的东西应该可以工作。但请注意,我必须将模型拆分到它们自己的文件中才能进行存根。可能有一种方法可以使用您当前的实现来做同样的事情。

    我建议的另一件事是在负责数据库实现的方法中加入某种用例。比如:

       const getAllItemsUseCase = (params, queryService) => {
        if(params.locationId){
            let items
            const item = await queryService.findOneLocation({
       };
    

    因此,当您从控制器调用此方法时,您可以调用:

    const getAllItems = async function(req, res) {
      const params = {
        locationId: req.query.location_id,
        // and more parameters
      };
    
      const queryService = {
        findOneLocation: Location.findOne,
      };
      const results = await getAllItemsUseCase(params, queryService);
    }
    

    通过这种方式,您可以将业务逻辑与控制器分离,并且可以更轻松地模拟您的查询:您只需更改提供给 queryService 的方法。

    您可以从这篇博文中找到一些有趣的读物:https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-07
      • 2019-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多