New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@hirez_io/observer-spy

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hirez_io/observer-spy - npm Package Compare versions

Comparing version 1.3.0 to 1.4.0

.prettierignore

129

CODE_OF_CONDUCT.md

@@ -1,11 +0,128 @@

# Contributor Code of Conduct
# Contributor Covenant Code of Conduct
As contributors and maintainers of jasmine-auto-spies, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.
## Our Pledge
Communication through any of channel (GitHub, Gitter, IRC, mailing lists, Google+, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to jasmine-auto-spies project to do the same.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
If any member of the community violates this code of conduct, the maintainers of jasmine-auto-spies may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate.
## Our Standards
This doc is based on [Angular's code of conduct](https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md)
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
conduct@hirez.io.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

127

CONTRIBUTING.md

@@ -1,2 +0,2 @@

# Contributing to observer-spy
# Contribution Guidelines

@@ -6,65 +6,98 @@ We would love for you to contribute to this project.

## 1. Be Kind - Code of Conduct
## Be Kind - Code of Conduct
Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md)
Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md) to help us keep this project open and inclusive.
## 2. Submitting an Issue
<br/>
You can file new issues by selecting from our [new issue templates](https://github.com/hirezio/observer-spy/issues/new/choose) and filling out the issue template.
## Found a bug? Want a feature? - Submit an Issue
## 3. Submitting a Pull Request (PR)
[Choose an issue template](https://github.com/hirezio/observer-spy/issues/new/choose) to file a bug report / feature request.
Before you submit your Pull Request (PR) consider the following guidelines:
1. Search [GitHub](https://github.com/hirezio/observer-spy/pulls) for an open or closed PR
that relates to your submission. You don't want to duplicate effort.
1. Be sure that **there is an issue** describes the problem you're fixing, or documents the design for the feature you'd like to add.
Discussing the design up front helps to ensure that we're ready to accept your work.
<br/>
1. Fork the this repo.
1. Make your changes in a new git branch:
## Ready to contribute a Pull Request (PR)?
```shell
git checkout -b my-fix-branch master
```
<br/>
1. Create your patch, **including appropriate test cases**.
1. Run `yarn test` to check if all the tests are passing.
and ensure that all tests pass.
1. Commit your changes using:
### ▶ 1. First - [Search this repo for existing PRs](https://github.com/hirezio/observer-spy/pulls) !
```shell
yarn commit
```
Try to find an open or closed PR that relates to the change you want to introduce.
This will create a descriptive commit message that follows our
[commit message conventions](#commit-message-format).
This is necessary to generate meaningful release notes automatically.
<br/>
1. Push your branch to GitHub:
### ▶ 2. **Before you start coding - [find](https://github.com/hirezio/observer-spy/issues) / [create an issue](https://github.com/hirezio/observer-spy/issues/new/choose)**
```shell
git push origin my-fix-branch
```
**Make sure there's an issue** describing the problem you're fixing, or documents the design for the feature you'd like to add.
Discussing the design up front helps to ensure that we're ready to accept your work.
1. In GitHub, send a pull request to `observer-spy:master`.
**Don't waste your time working on code before you got a 👍 in an issue comment.**
- If we suggest changes then:
<br/>
- Make the required updates.
- Re-run the tests to ensure tests are still passing.
- Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
```shell
git rebase master -i
git push -f
```
### ▶ 3. Fork the this repo and create a branch.
That's it! Thank you for your contribution!
Make your changes in a new git branch:
#### After your pull request is merged
```shell
git checkout -b my-fix-branch master
```
After your pull request is merged, you can safely delete your branch and pull the changes
from the main (upstream) repository:
<br/>
### ▶ 4. Make sure you add / modify tests
Run `yarn test:full` to make sure there aren't any errors
<br/>
### ▶ 5. Commit your changes using commitizen:
Instead of `git commit` use the following command:
```shell
yarn commit
```
It will then ask you a bunch of questions.
This will create a descriptive commit message that follows the
[Angular commit message convention](#commit-message-format).
This is necessary to generate meaningful release notes / CHANGELOG automatically.
<br/>
### ▶ 6. Push your branch to GitHub:
```shell
git push origin my-fix-branch
```
### ▶ 7. Create a PR
In GitHub, create a pull request for `hirezio/observer-spy:master`.
If you need to update your PR for some reason -
- Make the required updates.
- Re-run the tests to ensure tests are still passing `yarn test:full`
- Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
```shell
git rebase master -i
git push -f
```
<br/>
### ▶ 8. After your PR is merged - delete your branches
After your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) repository:
- Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:

@@ -94,9 +127,7 @@

<hr>
<br/>
This doc is based on [Angular's contributing document](https://github.com/angular/angular/blob/master/CONTRIBUTING.md)
### ▶ 9. That's it! Thank you for your contribution! 🙏💓
[coc]: CODE_OF_CONDUCT.md
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
[github]: https://github.com/hirezio/observer-spy
[stackblitz]: https://stackblitz.com/
export { ObserverSpy } from './observer-spy';
export { subscribeAndSpyOn, ObserverSpyWithSubscription } from './subscribe-and-spy-on';
export { ObserverSpyWithSubscription, SubscriberSpy } from './subscriber-spy';
export { subscribeSpyTo, subscribeAndSpyOn } from './subscribe-spy-to';
export { fakeTime } from './fake-time';
export { autoUnsubscribe, queueForAutoUnsubscribe } from './auto-unsubscribe';
//# sourceMappingURL=index.d.ts.map

@@ -5,7 +5,13 @@ "use strict";

exports.ObserverSpy = observer_spy_1.ObserverSpy;
var subscribe_and_spy_on_1 = require("./subscribe-and-spy-on");
exports.subscribeAndSpyOn = subscribe_and_spy_on_1.subscribeAndSpyOn;
exports.ObserverSpyWithSubscription = subscribe_and_spy_on_1.ObserverSpyWithSubscription;
var subscriber_spy_1 = require("./subscriber-spy");
exports.ObserverSpyWithSubscription = subscriber_spy_1.ObserverSpyWithSubscription;
exports.SubscriberSpy = subscriber_spy_1.SubscriberSpy;
var subscribe_spy_to_1 = require("./subscribe-spy-to");
exports.subscribeSpyTo = subscribe_spy_to_1.subscribeSpyTo;
exports.subscribeAndSpyOn = subscribe_spy_to_1.subscribeAndSpyOn;
var fake_time_1 = require("./fake-time");
exports.fakeTime = fake_time_1.fakeTime;
var auto_unsubscribe_1 = require("./auto-unsubscribe");
exports.autoUnsubscribe = auto_unsubscribe_1.autoUnsubscribe;
exports.queueForAutoUnsubscribe = auto_unsubscribe_1.queueForAutoUnsubscribe;
//# sourceMappingURL=index.js.map
import { Observer } from 'rxjs';
export interface ObserverState {
nextCalled: boolean;
errorCalled: boolean;
completeCalled: boolean;
nextWasCalled: boolean;
errorWasCalled: boolean;
completeWasCalled: boolean;
errorValue: any;

@@ -11,3 +11,3 @@ onCompleteCallback: (() => void) | undefined;

private onNextValues;
private observerState;
private state;
next(value: T): void;

@@ -14,0 +14,0 @@ error(errorVal: any): void;

@@ -6,6 +6,6 @@ "use strict";

this.onNextValues = [];
this.observerState = {
nextCalled: false,
errorCalled: false,
completeCalled: false,
this.state = {
nextWasCalled: false,
errorWasCalled: false,
completeWasCalled: false,
errorValue: undefined,

@@ -17,12 +17,12 @@ onCompleteCallback: undefined,

this.onNextValues.push(value);
this.observerState.nextCalled = true;
this.state.nextWasCalled = true;
};
ObserverSpy.prototype.error = function (errorVal) {
this.observerState.errorValue = errorVal;
this.observerState.errorCalled = true;
this.state.errorValue = errorVal;
this.state.errorWasCalled = true;
};
ObserverSpy.prototype.complete = function () {
this.observerState.completeCalled = true;
if (this.observerState.onCompleteCallback) {
this.observerState.onCompleteCallback();
this.state.completeWasCalled = true;
if (this.state.onCompleteCallback) {
this.state.onCompleteCallback();
}

@@ -32,11 +32,11 @@ };

var _this = this;
if (this.observerState.completeCalled) {
if (this.state.completeWasCalled) {
return callback ? callback() : Promise.resolve();
}
if (callback) {
this.observerState.onCompleteCallback = callback;
this.state.onCompleteCallback = callback;
return;
}
return new Promise(function (resolve) {
_this.observerState.onCompleteCallback = resolve;
_this.state.onCompleteCallback = resolve;
});

@@ -60,12 +60,12 @@ };

ObserverSpy.prototype.receivedNext = function () {
return this.observerState.nextCalled;
return this.state.nextWasCalled;
};
ObserverSpy.prototype.getError = function () {
return this.observerState.errorValue;
return this.state.errorValue;
};
ObserverSpy.prototype.receivedError = function () {
return this.observerState.errorCalled;
return this.state.errorWasCalled;
};
ObserverSpy.prototype.receivedComplete = function () {
return this.observerState.completeCalled;
return this.state.completeWasCalled;
};

@@ -72,0 +72,0 @@ return ObserverSpy;

{
"name": "@hirez_io/observer-spy",
"version": "1.3.0",
"version": "1.4.0",
"repository": {

@@ -5,0 +5,0 @@ "type": "git",

# @hirez_io/observer-spy 👀💪
A simple little class and a helper function that help make Observable testing a breeze
This library makes RxJS Observables testing easy!

@@ -10,4 +10,3 @@ [![npm version](https://img.shields.io/npm/v/@hirez_io/observer-spy.svg?style=flat-square)](https://www.npmjs.org/package/@hirez_io/observer-spy)

[![codecov](https://img.shields.io/codecov/c/github/hirezio/observer-spy.svg)](https://codecov.io/gh/hirezio/observer-spy) <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

@@ -24,17 +23,7 @@

## What's the problem?
Testing RxJS observables is usually hard, especially when testing advanced use cases.
This library:
✅ **Is easy to understand**
✅ **Reduces the complexity**
✅ **Makes testing advanced observables easy**
## Installation
```
```console
yarn add -D @hirez_io/observer-spy

@@ -45,91 +34,189 @@ ```

```
```console
npm install -D @hirez_io/observer-spy
```
## Observer Spies VS Marble Tests
<br/>
[Marble tests](https://rxjs-dev.firebaseapp.com/guide/testing/internal-marble-tests) are very powerful, but at the same time can be very complicated to learn and to reason about for some people.
## THE PROBLEM: Testing RxJS observables is hard! 😓
Especially when testing advanced use cases.
Until this library, the common way to test observables was to use [Marble tests](https://rxjs-dev.firebaseapp.com/guide/testing/internal-marble-tests)
### What are the disadvantages of Marble Tests?
Marble tests are very powerful, but unfortunately for most tests they are conceptually very complicated to learn and to reason about..
You need to learn and understand `cold` and `hot` observables, `schedulers` and to learn a new syntax just to test a simple observable chain.
More complex observable chains tests get even harder to read.
More complex observable chains tests get even harder to read and to maintain.
That's why this library was created - to present an alternative to marble tests, which we believe is cleaner and easier to understand and to use.
<br/>
<br/>
### How observer spies are cleaner?
## THE SOLUTION: Observer Spies! 👀💪
You generally want to test the outcome of your action, not implementation details like exactly how many frames were between each value.
The **Observer-Spy** library was created to present a viable alternative to Marble Tests.
The order of recieved values represents the desired outcome for most production app use cases.
An alternative which we believe is:
Most of the time, if enough (virtual) time passes until the expectation in my test, it should be sufficient to prove whether the expected outcome is valid or not.
* ✅ **Easier** to understand
## Usage
* ✅ **Reduces** the complexity
#### `new ObserverSpy()`
* ✅ Makes observables tests **cleaner**
<br/>
In order to test observables, you can use an `ObserverSpy` instance to "record" all the messages a source observable emits and to get them as an array.
## Why Observer-Spy is easier?
You can also spy on the `error` or `complete` states of the observer.
### 😮 Marble test:
You can use `done` or `async` / `await` to wait for `onComplete` to be called as well.
```js
**Example:**
import { TestScheduler } from 'rxjs/testing';
let scheduler: TestScheduler;
beforeEach(()=>{
scheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected)
})
})
it('should filter even numbers and multiply each number by 10', () => {
scheduler.run(({cold, expectObservable}) => {
const sourceValues = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10};
const source$ = cold('-a-b-c-d-e-f-g-h-i-j|', sourceValues);
const expectedOrder = '-a-b-c-d-e|';
const expectedValues = { a: 10, b: 30, c: 50, d: 70, e: 90};
const result$ = source$.pipe(
filter(n => n % 2 !== 0),
map(x => x * 10)
);
expectObservable(result$).toBe(expectedOrder, expectedValues);
})
});
```
### 😎 Observer Spy Test:
```js
// ... other imports
import { ObserverSpy } from '@hirez_io/observer-spy';
it('should spy on Observable values', () => {
const observerSpy = new ObserverSpy();
// BTW, if you're using TypeScript you can declare it with a generic:
// const observerSpy: ObserverSpy<string> = new ObserverSpy();
import { subscribeSpyTo } from '@hirez_io/observer-spy';
const fakeValues = ['first', 'second', 'third'];
const fakeObservable = of(...fakeValues);
it('should filter even numbers and multiply each number by 10', () => {
const result$ = of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).pipe(
filter(n => n % 2 !== 0),
map(x => x * 10)
);
const subscription = fakeObservable.subscribe(observerSpy);
const observerSpy = subscribeSpyTo(result$);
// DO SOME LOGIC HERE
expect(observerSpy.getValues()).toEqual([10, 30, 50, 70, 90]);
// unsubscribing is optional, it's good for stopping intervals etc
subscription.unsubscribe();
})
});
```
expect(observerSpy.receivedNext()).toBe(true);
expect(observerSpy.getValues()).toEqual(fakeValues);
You generally want to test the outcome of your action instead of implementation details [like how many frames were between each value].
expect(observerSpy.getValuesLength()).toEqual(3);
For most production app use cases, if enough (virtual) time passes testing the **received values** or their order should be sufficient.
This library gives you the tool to investigate your spy about the values it received and their order.
(The idea was inspired by [Reactive Programming with RxJava](https://books.google.co.il/books?id=y4Y1DQAAQBAJ))
<br/>
# Usage
<br/>
## `const observerSpy = subscribeSpyTo(observable)`
In order to test Observables you can use the `subscribeSpyTo` function:
```js
import { subscribeSpyTo } from '@hirez_io/observer-spy';
it('should immediately subscribe and spy on Observable ', () => {
const fakeObservable = of('first', 'second', 'third');
// get a special observerSpy of type "SubscriberSpy" (with an additional "unsubscribe" method)
// if you're using TypeScript you can declare it with a generic:
// const observerSpy: SubscriberSpy<string> ...
const observerSpy = subscribeSpyTo(fakeObservable);
// You can unsubscribe if you need to:
observerSpy.unsubscribe();
// EXPECTATIONS:
expect(observerSpy.getFirstValue()).toEqual('first');
expect(observerSpy.receivedNext()).toBe(true);
expect(observerSpy.getValues()).toEqual(fakeValues);
expect(observerSpy.getValuesLength()).toEqual(3);
expect(observerSpy.getFirstValue()).toEqual('first');
expect(observerSpy.getValueAt(1)).toEqual('second');
expect(observerSpy.getLastValue()).toEqual('third');
expect(observerSpy.receivedComplete()).toBe(true);
observerSpy.onComplete(() => {
expect(observerSpy.receivedComplete()).toBe(true);
}));
// --------------------------------------------------------
// You can also use this shorthand version:
expect(subscribeSpyTo(fakeObservable).getFirstValue()).toEqual('first');
// --------------------------------------------------------
});
```
it('should support async await for onComplete()', async ()=>{
const observerSpy = new ObserverSpy();
<br/>
### Wait for `onComplete` before expecting the result (using `async` + `await`)
```js
it('should support async await for onComplete()', async () => {
const fakeObservable = of('first', 'second', 'third');
fakeObservable.subscribe(observerSpy);
const observerSpy = subscribeSpyTo(fakeObservable);
await observerSpy.onComplete();
await observerSpy.onComplete(); // <-- the test will pause here until the observable is complete
expect(observerSpy.receivedComplete()).toBe(true);
// If you don't want to use async await you could pass a callback:
//
// observerSpy.onComplete(() => {
// expect(observerSpy.receivedComplete()).toBe(true);
// }));
});
```
<br/>
### Spy on errors with `receivedError` and `getError`
```js
it('should spy on Observable errors', () => {
const observerSpy = new ObserverSpy();
const fakeObservable = throwError('FAKE ERROR');
fakeObservable.subscribe(observerSpy);
const observerSpy = subscribeSpyTo(fakeObservable);

@@ -140,100 +227,169 @@ expect(observerSpy.receivedError()).toBe(true);

});
```
## Quick Usage with `subscribeAndSpyOn(observable)`
<br/>
You can also create an `ObserverSpy` and immediately subscribe to an observable with this simple helper function.
Observer spies generated that way will provide an additional `unsubscribe()` method that you might want to call
if your source observable does not complete or does not get terminated by an error while testing.
## Manually using `new ObserverSpy()`
**Example:**
You can create an `ObserverSpy` instance manually:
```js
import { subscribeAndSpyOn } from '@hirez_io/observer-spy';
// ... other imports
import { ObserverSpy } from '@hirez_io/observer-spy';
it('should immediately subscribe and spy on Observable ', () => {
const fakeObservable = of('first', 'second', 'third');
it('should spy on Observable values', () => {
const fakeValues = ['first', 'second', 'third'];
const fakeObservable = of(...fakeValues);
// get an "ObserverSpyWithSubscription"
const observerSpy = subscribeAndSpyOn(fakeObservable);
// and optionally unsubscribe
observerSpy.unsubscribe();
// BTW, if you're using TypeScript you can declare it with a generic:
// const observerSpy: ObserverSpy<string> = new ObserverSpy();
const observerSpy = new ObserverSpy();
expect(observerSpy.getFirstValue()).toEqual('first');
// This type of ObserverSpy doesn't have a built in "unsubscribe" method
// only the "SubscriberSpy" has it, so we need to create a separate "Subscription" variable.
const subscription = fakeObservable.subscribe(observerSpy);
// or use the shorthand version:
expect(subscribeAndSpyOn(fakeObservable).getFirstValue()).toEqual('first');
// ...DO SOME LOGIC HERE...
// unsubscribing is optional, it's good for stopping intervals etc
subscription.unsubscribe();
expect(observerSpy.getValuesLength()).toEqual(3);
});
```
# Testing Async Observables
<br/>
#### `it('should do something', fakeTime((flush) => { ... flush(); });`
You can use the `fakeTime` utility function and call `flush()` to simulate the passage of time if you have any async operators like `delay` or `timeout` in your tests.
# Auto Unsubscribing
### [SEE AN EXAMPLE HERE](#-for-time-based-rxjs-code-timeouts--intervals--animations---use-faketime)
### ⚠ PAY ATTENTION:
---
* This works **only with subscriptions created** using either `subscribeSpyTo()` or `queueForAutoUnsubscribe()`.
## Now, let's see some use cases and their solutions:
* Requires a global `afterEach` function, so **it only works** with frameworks like **Jasmine**, **Mocha** and **Jest**.
### ▶ For _Angular_ code - just use `fakeAsync`
You can control time in a much more versatile way and clear the microtasks queue (for promises) without using the `done()` which is much more convenient.
## `autoUnsubscribe()`
Just use `fakeAsync` (and `tick` if you need it).
In order to save you the trouble of calling `unsubscribe` in each test, you can configure the library to auto unsubscribe from every observer you create with `subscribeSpyTo()`.
Example:
### Configuring Jest with `autoUnsubscribe`
Add this to your jest configuration (i.e `jest.config.js`):
```js
// ... other imports
import { ObserverSpy } from '@hirez_io/observer-spy';
import { fakeAsync, tick } from '@angular/core/testing';
{
setupFilesAfterEnv: ['node_modules/@hirez_io/observer-spy/dist/setup-auto-unsubscribe.js'],
}
```
it('should test Angular code with delay', fakeAsync(() => {
### Configuring Angular with `autoUnsubscribe`
Add this to your `test.ts`
```ts
import { autoUnsubscribe } from '@hirez_io/observer-spy';
autoUnsubscribe();
```
### Manually adding a subscription with `queueForAutoUnsubscribe`
##
If you configured `autoUnsubscribe()` in your environment and want your manually created spies (via `new ObserverSpy()`) to be "auto unsubscribed" you can use `queueForAutoUnsubscribe(subscription)`.
It accepts any `Unsubscribable` object which has an `unsubscribe()` method -
```js
import { queueForAutoUnsubscribe } from '@hirez_io/observer-spy';
it('should spy on Observable values', () => {
const fakeValues = ['first', 'second', 'third'];
const fakeObservable = of(...fakeValues);
const observerSpy = new ObserverSpy();
const subscription = fakeObservable.subscribe(observerSpy)
// This will auto unsubscribe this subscription after the test ends
// (if you configured "autoUnsubscribe()" in your environment)
queueForAutoUnsubscribe(subscription);
const fakeObservable = of('fake value').pipe(delay(1000));
// ... rest of the test
const sub = fakeObservable.subscribe(observerSpy);
});
```
<br/>
tick(1000);
# Testing Sync Logic
sub.unsubscribe();
### ▶ Synchronous RxJS
expect(observerSpy.getLastValue()).toEqual('fake value');
}));
RxJS - without delaying operators or async execution contexts - will run synchronously. This is the simplest use case; where our `it()` does not need any special asynchronous plugins.
```ts
it('should run synchronously', () => {
const observerSpy = subscribeSpyTo(from(['first', 'second', 'third']));
expect(spy.getValuesLength()).toBe(3);
});
```
### ▶ For microtasks related code (promises, but no timeouts / intervals) - just use `async` `await` or `done()`
<br/>
You can use the `onComplete` method to wait for a completion before checking the outcome.
Chose between `async` + `await` or `done`, both work.
<br/>
Example:
# Testing Async Logic
If you're **not using Angular** and have RxJS async operators like `delay` or `timeout`
Use `fakeTime` with `flush()` to simulate the passage of time ([detailed explanation](#-for-time-based-rxjs-code-timeouts--intervals--animations---use-faketime)) -
[![image](https://user-images.githubusercontent.com/210413/85336618-83f92180-b4a4-11ea-800d-6bb275eeda45.png)](#-for-time-based-rxjs-code-timeouts--intervals--animations---use-faketime)
<br/>
### ▶ RxJS + Angular: use `fakeAsync`
With Angular, you can control time in a much more versatile way.
Just use `fakeAsync` (and `tick` if you need it):
```js
// ... other imports
import { ObserverSpy } from '@hirez_io/observer-spy';
import { subscribeSpyTo } from '@hirez_io/observer-spy';
import { fakeAsync, tick } from '@angular/core/testing';
it('should work with observables', async () => {
const observerSpy: ObserverSpy<string> = new ObserverSpy();
it('should test Angular code with delay', fakeAsync(() => {
const fakeObservable = of('fake value').pipe(delay(1000));
const fakeService = {
getData() {
return defer(() => of('fake data'));
},
};
const fakeObservable = of('').pipe(switchMap(() => fakeService.getData()));
const observerSpy = subscribeSpyTo(fakeObservable);
fakeObservable.subscribe(observerSpy);
tick(1000);
await observerSpy.onComplete();
expect(observerSpy.getLastValue()).toEqual('fake value');
}));
```
expect(observerSpy.getLastValue()).toEqual('fake data');
});
<br/>
### ▶ RxJS + Promises: use `async` + `await`
Since Promise(s) are [MicroTasks](https://javascript.info/microtask-queue), we should consider them to resolve asynchronously.
For code using _Promise(s)_ **without timeouts or intervals**, just use `async` + `await` with the `onComplete()` method:
```js
// ... other imports
import { subscribeSpyTo } from '@hirez_io/observer-spy';
it('should work with promises', async () => {
const observerSpy: ObserverSpy<string> = new ObserverSpy();

@@ -247,3 +403,3 @@ const fakeService = {

fakeObservable.subscribe(observerSpy);
const observerSpy = subscribeSpyTo(fakeObservable);

@@ -255,29 +411,16 @@ await observerSpy.onComplete();

it('should work with promises and "done()"', (done) => {
const observerSpy: ObserverSpy<string> = new ObserverSpy();
```
const fakeService = {
getData() {
return Promise.resolve('fake data');
},
};
const fakeObservable = defer(() => fakeService.getData());
<br/>
fakeObservable.subscribe(observerSpy);
### ▶ RxJS Timers / Animations: use `fakeTime`
observerSpy.onComplete(() => {
expect(observerSpy.getLastValue()).toEqual('fake data');
done();
});
});
```
RxJS code that has time-based logic (e.g using timeouts / intervals / animations) will emit asynchronously.
### ▶ For _time based_ rxjs code (timeouts / intervals / animations) - use `fakeTime`
`fakeTime()` is a custom utility function that wraps the test callback which is perfect for most of these use-cases.
`fakeTime` is a utility function that wraps the test callback.
It does the following things:
1. Changes the `AsyncScheduler` delegate to use `VirtualTimeScheduler` (which gives you the ability to use "virtual time" instead of having long tests)
2. Passes a `flush` function you can call to `flush()` the virtual time (pass time forward)
1. Changes the RxJS `AsyncScheduler` delegate to use `VirtualTimeScheduler` and use "virtual time".
2. Passes a `flush()` function you can call whenever you want to virtually pass time forward.
3. Works well with `done` if you pass it as the second parameter (instead of the first)

@@ -289,14 +432,12 @@

// ... other imports
import { ObserverSpy, fakeTime } from '@hirez_io/observer-spy';
import { subscribeSpyTo, fakeTime } from '@hirez_io/observer-spy';
it(
'should handle delays with a virtual scheduler',
fakeTime((flush) => {
it('should handle delays with a virtual scheduler', fakeTime((flush) => {
const VALUES = ['first', 'second', 'third'];
const observerSpy: ObserverSpy<string> = new ObserverSpy();
const delayedObservable: Observable<string> = of(...VALUES).pipe(delay(20000));
const sub = delayedObservable.subscribe(observerSpy);
flush();
sub.unsubscribe();
const observerSpy = subscribeSpyTo(delayedObservable);
flush(); // <-- passes the "virtual time" forward

@@ -307,12 +448,11 @@ expect(observerSpy.getValues()).toEqual(VALUES);

it(
'should handle be able to deal with done functionality as well',
fakeTime((flush, done) => {
// ===============================================================================
it('should handle done functionality as well', fakeTime((flush, done) => {
const VALUES = ['first', 'second', 'third'];
const observerSpy: ObserverSpy<string> = new ObserverSpy();
const delayedObservable: Observable<string> = of(...VALUES).pipe(delay(20000));
const sub = delayedObservable.subscribe(observerSpy);
const observerSpy = subscribeSpyTo(delayedObservable);
flush();
sub.unsubscribe();

@@ -327,10 +467,35 @@ observerSpy.onComplete(() => {

### ▶ For _ajax_ calls (http) - they shouldn't be tested in a unit / micro test anyway... 😜
<br/>
Yeah. Test those in an integration test!
### ▶ RxJS + _AJAX_ calls:
# Wanna learn more?
Asynchronous REST calls (using axios, http, fetch, etc.) should not be tested in a unit / micro test... Test those in an integration test! 😜
## In my [class testing In action course](http://testangular.com/?utm_source=github&utm_medium=link&utm_campaign=observer-spy) I go over all the differences and show you how to use this library to test stuff like `switchMap`, `interval` etc...
<br/>
<br/>
# 🧠 Wanna become a PRO Observables tester?
In [Angular Class Testing In action](http://testangular.com/?utm_source=github&utm_medium=link&utm_campaign=observer-spy) course Shai Reznik goes over all the differences and show you how to use observer spies to test complex Observable chains with `switchMap`, `interval` etc...
<br/>
<br/>
## Contributing
Want to contribute? Yayy! 🎉
Please read and follow our [Contributing Guidelines](CONTRIBUTING.md) to learn what are the right steps to take before contributing your time, effort and code.
Thanks 🙏
<br/>
## Code Of Conduct
Be kind to each other and please read our [code of conduct](CODE_OF_CONDUCT.md).
<br/>
## Contributors ✨

@@ -348,2 +513,4 @@

<td align="center"><a href="https://github.com/burkybang"><img src="https://avatars0.githubusercontent.com/u/927886?v=4" width="100px;" alt=""/><br /><sub><b>Adam Smith</b></sub></a><br /><a href="https://github.com/hirezio/observer-spy/commits?author=burkybang" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/katharinakoal"><img src="https://avatars3.githubusercontent.com/u/17751573?v=4" width="100px;" alt=""/><br /><sub><b>Katharina Koal</b></sub></a><br /><a href="https://github.com/hirezio/observer-spy/commits?author=katharinakoal" title="Code">💻</a> <a href="https://github.com/hirezio/observer-spy/commits?author=katharinakoal" title="Tests">⚠️</a> <a href="https://github.com/hirezio/observer-spy/commits?author=katharinakoal" title="Documentation">📖</a> <a href="#ideas-katharinakoal" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/hirezio/observer-spy/issues?q=author%3Akatharinakoal" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://www.linkedin.com/in/thomasburleson"><img src="https://avatars3.githubusercontent.com/u/210413?v=4" width="100px;" alt=""/><br /><sub><b>Thomas Burleson</b></sub></a><br /><a href="https://github.com/hirezio/observer-spy/commits?author=ThomasBurleson" title="Code">💻</a> <a href="https://github.com/hirezio/observer-spy/commits?author=ThomasBurleson" title="Tests">⚠️</a> <a href="https://github.com/hirezio/observer-spy/commits?author=ThomasBurleson" title="Documentation">📖</a> <a href="#ideas-ThomasBurleson" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>

@@ -354,5 +521,15 @@ </table>

<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
<br/>
## License
MIT
export { ObserverSpy } from './observer-spy';
export { subscribeAndSpyOn, ObserverSpyWithSubscription } from './subscribe-and-spy-on';
export { ObserverSpyWithSubscription, SubscriberSpy } from './subscriber-spy';
export { subscribeSpyTo, subscribeAndSpyOn } from './subscribe-spy-to';
export { fakeTime } from './fake-time';
export { autoUnsubscribe, queueForAutoUnsubscribe } from './auto-unsubscribe';

@@ -6,5 +6,5 @@ import { Observable, of, throwError } from 'rxjs';

describe('ObserverSpy', () => {
describe(`GIVEN observable emits 3 values and completes
describe(`GIVEN the observable emits 3 values and completes
WHEN subscribing`, () => {
function getObservableWith3Values() {
function getSpyAndObservableWith3Values() {
const observerSpy: ObserverSpy<string> = new ObserverSpy();

@@ -22,3 +22,3 @@ const fakeValues: any[] = ['first', 'second', 'third'];

it('should set receivedNext to true', () => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -31,3 +31,7 @@ fakeObservable.subscribe(observerSpy).unsubscribe();

it('should return the right values', () => {
const { observerSpy, fakeObservable, fakeValues } = getObservableWith3Values();
const {
observerSpy,
fakeObservable,
fakeValues,
} = getSpyAndObservableWith3Values();

@@ -40,3 +44,3 @@ fakeObservable.subscribe(observerSpy).unsubscribe();

it('should return the values length of 3', () => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -49,3 +53,3 @@ fakeObservable.subscribe(observerSpy).unsubscribe();

it('should be able to return the correct first value', () => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -58,3 +62,3 @@ fakeObservable.subscribe(observerSpy).unsubscribe();

it('should be able to return the correct value at any index', () => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -67,3 +71,3 @@ fakeObservable.subscribe(observerSpy).unsubscribe();

it('should be able to return the correct last value', () => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -76,3 +80,3 @@ fakeObservable.subscribe(observerSpy).unsubscribe();

it('should know whether it got a "complete" notification', () => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -85,3 +89,3 @@ fakeObservable.subscribe(observerSpy).unsubscribe();

it('should be able to call a callback when it completes synchronously', (done) => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -97,3 +101,3 @@ fakeObservable.subscribe(observerSpy);

it('should return a resolved promise when it completes synchronously', async () => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -107,3 +111,3 @@ fakeObservable.subscribe(observerSpy);

it('should be able to call a callback when it completes asynchronously', (done) => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -119,3 +123,3 @@ fakeObservable.pipe(delay(1)).subscribe(observerSpy);

it('should return a resolved promise when it completes asynchronously', async () => {
const { observerSpy, fakeObservable } = getObservableWith3Values();
const { observerSpy, fakeObservable } = getSpyAndObservableWith3Values();

@@ -122,0 +126,0 @@ fakeObservable.pipe(delay(1)).subscribe(observerSpy);

import { Observer } from 'rxjs';
export interface ObserverState {
nextCalled: boolean;
errorCalled: boolean;
completeCalled: boolean;
nextWasCalled: boolean;
errorWasCalled: boolean;
completeWasCalled: boolean;
errorValue: any;

@@ -14,6 +14,6 @@ onCompleteCallback: (() => void) | undefined;

private observerState: ObserverState = {
nextCalled: false,
errorCalled: false,
completeCalled: false,
private state: ObserverState = {
nextWasCalled: false,
errorWasCalled: false,
completeWasCalled: false,
errorValue: undefined,

@@ -25,14 +25,14 @@ onCompleteCallback: undefined,

this.onNextValues.push(value);
this.observerState.nextCalled = true;
this.state.nextWasCalled = true;
}
error(errorVal: any): void {
this.observerState.errorValue = errorVal;
this.observerState.errorCalled = true;
this.state.errorValue = errorVal;
this.state.errorWasCalled = true;
}
complete(): void {
this.observerState.completeCalled = true;
if (this.observerState.onCompleteCallback) {
this.observerState.onCompleteCallback();
this.state.completeWasCalled = true;
if (this.state.onCompleteCallback) {
this.state.onCompleteCallback();
}

@@ -44,3 +44,3 @@ }

onComplete(callback?: () => void) {
if (this.observerState.completeCalled) {
if (this.state.completeWasCalled) {
return callback ? callback() : Promise.resolve();

@@ -50,3 +50,3 @@ }

if (callback) {
this.observerState.onCompleteCallback = callback;
this.state.onCompleteCallback = callback;
return;

@@ -56,3 +56,3 @@ }

return new Promise((resolve) => {
this.observerState.onCompleteCallback = resolve;
this.state.onCompleteCallback = resolve;
});

@@ -82,16 +82,16 @@ }

receivedNext(): boolean {
return this.observerState.nextCalled;
return this.state.nextWasCalled;
}
getError(): any {
return this.observerState.errorValue;
return this.state.errorValue;
}
receivedError(): boolean {
return this.observerState.errorCalled;
return this.state.errorWasCalled;
}
receivedComplete(): boolean {
return this.observerState.completeCalled;
return this.state.completeWasCalled;
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc