使用practicalmeteor:mocha测试Meteor订阅时遇同步超时求方案
解决Meteor客户端测试中Mocha超时的问题
看起来你的问题主要出在异步逻辑混用、测试环境未清理,以及数据同步等待的判断条件不够精准这几个点上,我来一步步帮你修复:
1. 别混用async和done
Mocha对async函数的处理是自动等待其返回的Promise完成,同时又传入done回调的话,会导致异步逻辑混乱——Mocha可能既在等Promise,又在等done触发,很容易超时。直接用async/await统一处理所有异步操作就好。
2. 每次测试前清空集合,避免残留数据
你的count === 1条件永远不满足的大概率原因是:之前测试的记录没被清理,导致客户端集合里有旧数据,count永远大于1。你需要在beforeEach开头调用一个服务器端的Meteor方法来清空集合(客户端默认没有直接写入集合的权限):
先在服务器端添加清空方法:
Meteor.methods({ 'topics.clear': function() { // 仅在测试环境执行,避免误操作生产数据 if (Meteor.isTest || Meteor.isAppTest) { TopicsCollection.remove({}); } } });
3. 精准等待订阅和数据同步
subscription.ready()只能说明订阅已经建立,但客户端集合的数据可能还没完成同步。直接等待目标记录出现在客户端集合里,比判断count更可靠。
修复后的完整代码
describe('a topic', function() { beforeEach(async function() { // 第一步:清空集合,确保测试环境干净 await new Promise((resolve, reject) => { Meteor.call('topics.clear', (err) => { if (err) reject(err); else resolve(); }); }); // 第二步:调用方法创建新topic const topicId = await new Promise((resolve, reject) => { Meteor.call('topics.createNew', (err, res) => { if (err) reject(err); else resolve(res); }); }); // 第三步:订阅目标topic const subscription = Meteor.subscribe('topics.details', topicId); // 等待订阅完成 await new Promise((resolve) => { Tracker.autorun((computation) => { if (subscription.ready()) { computation.stop(); resolve(); } }); }); // 第四步:确认目标记录已同步到客户端集合 await new Promise((resolve) => { Tracker.autorun((computation) => { const targetTopic = TopicsCollection.findOne(topicId); if (targetTopic) { computation.stop(); resolve(); } }); }); }); it('should be present in the client collection', function() { const topicCount = TopicsCollection.find({}).count(); expect(topicCount).to.equal(1); // 更精准的验证:检查刚创建的topic是否存在 const createdTopic = TopicsCollection.findOne(); expect(createdTopic).to.exist; }); });
额外优化建议
你可以把等待订阅、等待数据的逻辑封装成通用辅助函数,避免重复代码:
// 辅助函数:等待订阅完成 const waitForSubscription = (subscription) => { return new Promise((resolve) => { Tracker.autorun((computation) => { if (subscription.ready()) { computation.stop(); resolve(); } }); }); }; // 辅助函数:等待指定记录出现在集合中 const waitForDocument = (collection, docId) => { return new Promise((resolve) => { Tracker.autorun((computation) => { const doc = collection.findOne(docId); if (doc) { computation.stop(); resolve(doc); } }); }); };
这样beforeEach可以简化成:
beforeEach(async function() { await new Promise((resolve, reject) => { Meteor.call('topics.clear', (err) => err ? reject(err) : resolve()); }); const topicId = await new Promise((resolve, reject) => { Meteor.call('topics.createNew', (err, res) => err ? reject(err) : resolve(res)); }); const subscription = Meteor.subscribe('topics.details', topicId); await waitForSubscription(subscription); await waitForDocument(TopicsCollection, topicId); });
内容的提问来源于stack exchange,提问作者Rockettomatoo
相关产品推荐
相关产品推荐

