如何用practicalmeteor:mocha在Meteor单元测试中模拟已登录用户?
解决Meteor方法单元测试中已登录用户模拟的问题
你遇到的错误核心原因是:Meteor.userId()必须在合法的方法调用上下文(即带有userId的invocation对象)中执行,而你之前只模拟了Meteor.user(),没有正确构建方法调用的上下文环境,也没有处理Meteor.userId()的调用逻辑。
问题根源拆解
Meteor的方法内部,Meteor.userId()本质是返回当前调用上下文(invocation)的this.userId,而非单纯依赖Meteor.user()的返回值。当你直接调用Meteor.server.method_handlers里的函数时,必须手动构建包含userId的invocation对象,同时确保Meteor.userId()的stub逻辑正确。
修正后的测试代码
import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { assert } from 'meteor/practicalmeteor:chai'; import { sinon } from 'meteor/practicalmeteor:sinon'; import { Carts } from '/imports/api/carts/carts.js'; import { Articles } from '/imports/api/articles/articles.js'; import '/imports/api/carts/carts.methods.js'; import { SecurityEnsurer } from '/lib/security/security.js'; function randomInt (low, high) { return Math.floor(Math.random() * (high - low) + low); } if (Meteor.isServer) { describe('Carts', () => { describe('methods', () => { let currentUser, articleId; beforeEach(() => { // 避免重复定义工厂报错 if (Factory.factories.user) Factory.remove('user'); // 修正Meteor用户的默认字段结构 Factory.define('user', Meteor.users, { name: "userTest", emails: [{ address: 'user@shop.info', verified: false }], }); currentUser = Factory.create('user'); // 同时Stub两个核心用户函数 sinon.stub(Meteor, 'user').returns(currentUser); sinon.stub(Meteor, 'userId').returns(currentUser._id); // 清空测试集合 Articles.remove({}); Carts.remove({}); // 创建测试商品 articleId = Articles.insert({ name : "azerty", description : "descTest", modelNumber : "wxcvbn", categoryName : "CatTest", price : 1, advisedPrice: 2, supplierDiscount : 0, brandId : "BrandTest", isAvailable: true, restockingTime: 42, color: "Yellow", technicals: [{ name : "GPU", value : "Intel" }], }); // 购物车owner存储用户_id(MongoDB关联常规做法) Carts.insert({ owner: currentUser._id, entries: [], }); }); afterEach(() => { // 恢复所有Stub Meteor.user.restore(); Meteor.userId.restore(); // 清空测试数据 Articles.remove({}); Carts.remove({}); Factory.remove('user'); }); it('can add article to cart', () => { const quantity = randomInt(1,50); const addArticleToCart = Meteor.server.method_handlers['carts.addArticle']; // 构建合法的调用上下文,包含userId const invocation = { userId: currentUser._id }; // 把invocation作为this传入方法调用 addArticleToCart.apply(invocation, [articleId, quantity]); // 更直观的断言逻辑 const userCart = Carts.findOne({ owner: currentUser._id }); assert.isNotNull(userCart); assert.equal(userCart.entries.length, 1); assert.equal(userCart.entries[0].articleId, articleId); assert.equal(userCart.entries[0].quantity, quantity); }); }); }); }
关键修改说明
- 修正用户工厂结构:Meteor用户的
emails字段默认是数组格式,而非自定义的currentUser字段,符合官方用户文档结构。 - 关联字段规范:购物车的
owner存储用户_id字符串,而非整个用户对象,这是MongoDB数据关联的标准做法。 - 双Stub保障:同时模拟
Meteor.user()和Meteor.userId(),覆盖方法中可能的两种调用场景。 - 合法调用上下文:构建包含
userId的invocation对象,并作为this传入方法调用,让Meteor.userId()能正确识别当前用户。 - 直观断言逻辑:直接查询用户购物车并验证条目内容,比用
count()更精准。
这样修改后,你的测试就能正确模拟已登录用户的状态,顺利执行需要权限的购物车添加方法。
内容的提问来源于stack exchange,提问作者Noirdesir
相关产品推荐
相关产品推荐

