如何在 Angular 应用程序的 Karma 测试中模拟 Firebase

2023-12-22

通过遵循 AngularFire 指南,我已将作用域变量与 Firebase 数组同步。我的代码与教程基本相同(第5步):

https://www.firebase.com/docs/web/libraries/angular/quickstart.html https://www.firebase.com/docs/web/libraries/angular/quickstart.html

我的应用程序中的所有内容都正常工作,但我对如何在 Karma 单元测试中正确模拟 Firebase 调用感到非常困惑。我猜想类似于使用 $provide 来模拟数据?但是 $add 在我的控制器方法中不起作用。帮助?


复制自这个要点 https://gist.github.com/katowulf/3e6ec8b109d76e63d7ac4046bf09ef77其中详细讨论了这个主题。

讨论全部内嵌在评论中。这里有很多需要考虑的地方。

模拟出了什么问题

考虑这个数据结构,用于根据成员身份获取名称列表

/users/<user id>/name
/rooms/members/<user id>/true 

现在让我们创建几个简单的类,而不需要真正考虑测试结构,假设我们将使用 Firebase 的模拟来测试它们。请注意,我经常在野外看到此类错误;这个例子并不牵强或夸张(相比之下它相当温和)

class User {
  // Accepts a Firebase snapshot to create the user instance
  constructor(snapshot) {
    this.id = snapshot.key;
    this.name = snapshot.val().name;
  }

  getName() { return this.name; }

  // Load a user based on their id
  static load(id) {
     return firebase.database().ref('users').child(uid)
        .once('value').then(snap => new User(snap));
  }
}

class MembersList {
   // construct a list of members from a Firebase ref
   constructor(memberListRef) {
     this.users = [];

     // This coupling to the Firebase SDK and the nuances of how realtime is handled will
     // make our tests pretty difficult later.
     this.ref = memberListRef;
     this.ref.on('child_added', this._addUser, this);
   }

   // Get a list of the member names
   // Assume we need this for UI methods that accept an array, so it can't be async
   // 
   // It may not be obvious that we've introduced an odd coupling here, since we 
   // need to know that this is loaded asynchronously before we can use it.
   getNames() {
     return this.users.map(user => user.getName());
   }

   // So this kind of stuff shows up incessantly when we couple Firebase into classes and
   // it has a big impact on unit testing (shown below)
   ready() {
      // note that we can't just use this.ref.once() here, because we have to wait for all the
      // individual user objects to be loaded, adding another coupling on the User class's internal design :(
      return Promise.all(this.promises);
   }

   // Asynchronously find the user based on the uid
   _addUser(memberSnap) {
      let promise = User.load(memberSnap.key).then(user => this.users.push(user));
      // note that this weird coupling is needed so that we can know when the list of users is available
      this.promises.push(promise);
   }

   destroy() {
      this.ref.off('child_added', this._addUser, this);
   }
}

/*****
 Okay, now on to the actual unit test for list names.
 ****/

const mockRef = mockFirebase.database(/** some sort of mock data */).ref();

// Note how much coupling and code (i.e. bugs and timing issues we might introduce) is needed
// to make this work, even with a mock
function testGetNames() {
   const memberList = new MemberList(mockRef); 

   // We need to abstract the correct list of names from the DB, so we need to reconstruct
   // the internal queries used by the MemberList and User classes (more opportunities for bugs
   // and def. not keeping our unit tests simple)
   //
   // One important note here is that our test unit has introduced a side effect. It has actually cached the data
   // locally from Firebase (assuming our mock works like the real thing; if it doesn't we have other problems)
   // and may inadvertently change the timing of async/sync events and therefore the results of the test!
   mockRef.child('users').once('value').then(userListSnap => {
     const userNamesExpected = [];
     userListSnap.forEach(userSnap => userNamesExpected.push(userSnap.val().name));

     // Okay, so now we're ready to test our method for getting a list of names
     // Note how we can't just test the getNames() method, we also have to rely on .ready()
     // here, breaking our effective isolation of a single point of logic.
     memberList.ready().then(() => assertEqual(memberList.getNames(), userNamesExpected));

     // Another really important note here: We need to call .off() on the Firebase
     // listeners, or other test units will fail in weird ways, since callbacks here will continue
     // to get invoked when the underlying data changes.
     //
     // But we'll likely introduce an unexpected bug here. If assertEqual() throws, which many testing
     // libs do, then we won't reach this code! Yet another strange, intermittent failure point in our tests
     // that will take forever to isolate and fix. This happens frequently; I've been a victim of this bug. :(
     memberList.destroy(); // (or maybe something like mockFirebase.cleanUpConnections()
   });
}

通过适当的封装和 TDD 获得更好的方法

好的,现在让我们从有效的测试单元设计开始扭转这种情况,看看我们是否可以设计出更少耦合的类来适应。

function testGetNames() {
  const userList = new UserList();
  userList.add( new User('kato', 'Kato Richardson') );
  userList.add( new User('chuck', 'Chuck Norris') );
  assertEqual( userList.getNames(), ['Kato Richardson', 'Chuck Norris']);
  // Ah, this is looking good! No complexities, no async madness. No chance of bugs in my unit test!
}

/**
 Note how our classes will be simpler and better designed just by using a good TDD
 */

class User {
   constructor(userId, name) {
      this.id = userId; 
      this.name = name;
   }

   getName() {
      return this.name; 
   }
}

class UserList {
  constructor() {
    this.users = []; 
  }

  getNames() {
    return this.users.map(user => user.getName()); 
  }

  addUser(user) {
    this.users.push(user);
  }
  // note how we don't need .destroy() and .ready() methods here just to know
  // when the user list is resolved, yay!
}

// This all looks great and wonderful, and the tests are going to run wonderfully.
// But how do we populate the list from Firebase now?? The answer is an isolated
// service that handles this.

class MemberListManager {
   constructor( memberListRef ) {
     this.ref = memberListRef;
     this.ref.on('child_added', this._addUser, this);
     this.userList = new UserList();
   }

   getUserList() {
      return this.userList; 
   }

   _addUser(snap) {
     const user = new User(snap.key, snap.val().name);
     this.userList.push(user);
   }

   destroy() {
      this.ref.off('child_added', this._addUser, this); 
   }
}

// But now we need to test MemberListManager, too, right? And wouldn't a mock help here? Possibly. Yes and no.
//
// More importantly, it's just one small service that deals with the async and external libs.
// We don't have to depend on mocking Firebase to do this either. Mocking the parts used in isolation
// is much, much simpler than trying to deal with coupled third party dependencies across classes.
// 
// Additionally, it's often better to move third party calls like these
// into end-to-end tests instead of unit tests (since we are actually testing
// across third party libs and not just isolated logic)
//
// For more alternatives to a mock sdk, check out AngularFire.
// We often just used the real Firebase Database with set() or push(): 
// https://github.com/firebase/angularfire/blob/master/tests/unit/FirebaseObject.spec.js#L804
//
// An rely on spies to stub some fake snapshots or refs with results we want, which is much simpler than
// trying to coax a mock or SDK to create error conditions or specific outputs:
// https://github.com/firebase/angularfire/blob/master/tests/unit/FirebaseObject.spec.js#L344
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Angular 应用程序的 Karma 测试中模拟 Firebase 的相关文章

随机推荐

  • 如何保护 firebase Cloud Function HTTP 端点以仅允许经过 Firebase 身份验证的用户?

    借助新的 firebase 云功能 我决定将部分 HTTP 端点移至 firebase 一切都很好 但我有以下问题 我有两个由 HTTP 触发器 云函数 构建的端点 用于创建用户并返回自定义令牌的 API 端点 由 Firebase Adm
  • 删除一组具有特定值的变量中的随机观察子集

    假设我有一个数据框 我想在其中删除满足特定标准的随机观察样本 即整行 以此数据框为例 id var1 var2 AAAA 1 Car BBBB 2 Truck CCCC 1 Boat DDDD 2 Car EEEE 1 Truck FFFF
  • 为传单设置 SRID - Geodjango

    我的 Postgres 数据库中有 31277 投影的数据 无论如何 我正在使用 Leaflet 地图来向他们展示 Geodjango 现在 我的数据已移动到地图中的某个位置 因此我需要更改 Leaflet 地图的投影 在文档中 http
  • Heroku 计费测功机到底如何工作?

    我试图了解 Heroku 定价系统 好的 免费帐户每小时可获得 1000 dyno dyno hour 的确切含义是什么 这取决于什么 每秒请求数 用户数量 如果我想切换到非睡眠优惠 我必须为每个 dyno 支付 7 美元 那么 1000
  • 友元函数未在此范围内声明错误

    您好 我试图了解友元函数的范围 但出现 未在范围内声明 错误 这是我的代码 node h class Node public int id int a int b friend int add int int void itsMyLife
  • 在 Python 3 中使用 xlsxwriter 交替行颜色

    有人在Python3中使用xlsxwriter生成excel时实现了交替行颜色吗 data format workbook add format bg color FFC7CE worksheet write data row data c
  • vim 系统寄存器 * 和 + 不起作用

    echo has clipboard 返回 1 但每当我执行 yy or yy 那些寄存器中似乎什么也没有 如果我使用常规yy复制另一行文本 然后尝试使用从寄存器粘贴CONTROL V什么都没发生 如果我尝试 pvim 粘贴我使用常规复制的
  • Linux 的 Windows Beep() 等效项

    我正在Windows上试验Beep功能 include
  • Laravel 创建方法

    我正在尝试使用 Laravel 存储数组create method input Input all new media this gt media gt create input or input Input all new media M
  • 为输入字段注册自定义焦点事件处理程序的最佳实践

    遗憾的是 我没有找到任何焦点事件sap m Input or sap m TextArea我可以在 XML 视图中注册处理程序 您的最佳实践是什么 例如如果你有近 100 个字段并且其中大部分应该处理focus 事件 以便自动选择输入字段中
  • 转换为具有指定小数位数的小数

    有没有办法将一个数字转换为具有指定小数位数的小数 我试过 SELECT CAST NumericField AS NUMERIC 15 DecimalPlaces AS NumericField 但这没有用 EDIT 我写错了NUMBER代
  • Chrome 开发者工具 - (索引)样式表在哪里?

    我正在修改一个基于 WordPress 的网站的主题 新主题再具体一点 虽然我改变了主题选项 style css文件和custom css文件 我无法更改链接的颜色 然后我使用 Chrome 的开发者工具来检查我的规则在哪里被覆盖 最重要的
  • 如何在Web Core API中调试启动?

    我有一个使用 Web Core API 的 MVC 网站 在进行了微小的更改和部署后 我意外地收到了错误 响应状态代码不表示成功 500 内部服务器错误 所以我启用了 Web Core API 的日志文件 请参阅https learn mi
  • ASP.NET MVC 应用程序中的数据操作和业务逻辑代码应该放在哪里?

    观看了 Rob Conery 的 Kona 应用程序的示例后 我发现他在 IoC 中使用了两个东西 ISession 其中有数据层代码和服务 其中有一些我们在操作数据存储中的数据时需要执行的附加业务逻辑 例如 我们可能不仅向数据库添加一条记
  • PHP 中的简单 BBparser 可让您替换标签之外的内容

    我正在尝试解析表示源代码的字符串 如下所示 code lang html lt div gt stuff lt div gt code div stuff div 正如你从我之前的 20 个问题中看到的 我尝试使用 PHP 的正则表达式函数
  • 向 Woocommerce 3.0 添加股票期权

    我正在尝试将自定义 stock status 添加到 WordPress 中的 woocommerce 3 0 中 最终目标是在产品编辑页面上添加第三个库存选项 暂停 并在产品页面上显示该库存状态 以前我可以使用这里的方法 在 woocom
  • 由于 CPU 类型的原因,C++ Boost 多线程比单线程慢?

    我之前发布过一些boost多线程 这次我只是好奇和失望 因为我认为多线程应该比单线程更快 两个线程是 FILE I O 读取 解析 CSV 数据 当我使用多线程时 每台来自 DELL DESKTOP OPTIPLEX 745 的 PENTI
  • PHP 7.2 count() 函数不起作用

    我有一个正在运行的 php 应用程序 它在 php 7 0 版本上运行良好 但是当我将 php 版本升级到 7 2 时 我收到此错误 count Parameter must be an array or an object that im
  • 使用 Subversive 时出现 Eclipse 错误

    我正在关注此处说明 http www headfirstandroid com p guide importing projects from google html获取存储库Head First Android 我已经安装了Subvers
  • 如何在 Angular 应用程序的 Karma 测试中模拟 Firebase

    通过遵循 AngularFire 指南 我已将作用域变量与 Firebase 数组同步 我的代码与教程基本相同 第5步 https www firebase com docs web libraries angular quickstart