@rxjs-stuff/marbles
Version:
A set of plugins that provide a natural feeling integration with Mocha and Chai for RxJS "marbles" testing.
230 lines (171 loc) • 6.16 kB
Markdown
# @rxjs-stuff/marbles
`@rxjs-stuff/marbles` provides a natural feeling integration with Mocha and Chai for RxJS "marbles" testing.
See the [RxJS documentation on Marble Tests](https://rxjs-dev.firebaseapp.com/guide/testing/internal-marble-tests) for
more information.
```typescript
import { expect } from 'chai'
describe.marbles('simple example', ({ cold }) => {
it('emits and closes', () => {
const example$ = cold('--a|')
const expected = '--a|'
expect(example$).to.equal(expected)
})
})
```
## Configuration
### NodeJS
Add the Mocha and Chai integrations from `@rxjs-stuff/marbles` in your `mocha.config.js` file:
```javascript
const chai = require('chai')
const { chaiMarbles, config } = require('@rxjs-stuff/marbles/chai')
chai.use(chaiMarbles)
require('@rxjs-stuff/marbles/mocha/node').mochaMarbles(config)
```
This file must be run before any specs, so it is easiest to include as a `require` entry in your
mocha config.
### Karma (Browsers)
Use with either the `webpack-karma` plugin or the Angular CLI, and load `@rxjs-stuff/marbles/karma`
from your entry point. No other changes are required for `karma.conf.js`.
#### Angular
Include `@rxjs-stuff/marbles/karma` after Angular's initialization:
```typescript
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting())
import '@rxjs-stuff/marbles/karma'
```
#### Non-Angular
For non-Angular uses, use the "bundle" approach for loading your test files:
```typescript
import '@rxjs-stuff/marbles/karma'
declare const require: {
context(
path: string,
deep?: boolean,
filter?: RegExp,
): {
keys(): string[]
<T>(id: string): T
}
}
const context = require.context('./src', true, /\.spec\.ts$/)
context.keys().map(context)
```
### TypeScript (NodeJS or Browser)
Add `node_modules/@rxjs-stuff/marbles/@types` to your `tsconfig.json`'s `typeRoots`:
```json
{
"compilerOptions": {
...
"typeRoots": [
"node_modules/@rxjs-stuff/marbles/@types",
"node_modules/@types"
]
}
}
```
`@rxjs-stuff/marbles` augments ambient types from Mocha and Chai in order to add the `.marbles`
modifier, as well as additional Chai assertions. Since these do not require importing from
`@rxjs-stuff/marbles` in the modules where those would be referenced, the additional `typeRoots`
entry is required to allow TypeScript to pick up the augmented types.
## Usage
`@rxjs-stuff/marbles` provides API extensions for Mocha and Chai that allow you to do marbles testing
using the same syntax and patterns you're already used to.
### Mocha
Use the `.marbles` chain when defining a suite to enable marbles testing for all tests and nested suites.
When marbles testing is enabled, the framework automatically takes care of setting up a `TestScheduler`
instance, as well as calling `TestScheduler.flush` for each test.
The suite definition function will be passed an object with the same `RunHelpers` utilities provided
when calling RxJS's `TestScheduler.run`.
```typescript
describe.marbles('example', ({ cold, hot }) => {
...
})
```
The usual suite definition modifiers are also supported when using `describe.marbles`:
```typescript
xdescribe.marbles(...)
describe.marbles.only(...)
describe.marbles.skip(...)
```
To disable marbles testing within a nested suite, use `describe.noMarbles`:
```typescript
describe.marbles('marbles tests', () => {
describe.noMarbles('non marbles tests', () => {
})
})
```
### Chai
`@rxjs-stuff/marbles/chai` adds several language chain functions and properties to assist in configuring
your assertions, and also overrides `equal` to automatically handle comparisons between Observables
and marbles strings.
#### Basic Comparison
You do not need to use `expectObservable` to compare marbles - Chai's existing `equal` assertion is
overridden to use `expectObservable` internally.
```typescript
import { expect } from 'chai'
describe.marbles('simple example', ({ cold }) => {
it('emits and completes', () => {
const example$ = cold('--a|')
const expected = '--a|'
expect(example$).to.equal(expected)
})
})
```
#### Using marbleValues
Use `.with.marbleValues(...)` to compare observables using a value map for marbles events.
```typescript
import { expect } from 'chai'
describe.marbles('simple example', ({ cold }) => {
it('emits and completes', () => {
const marbleValues = { a: { foo: 'bar' }}
const example$ = cold('--a|', marbleValues)
const expected = '--a|'
expect(example$).with.marbleValues(marbleValues).to.equal(expected)
})
})
```
#### Comparing Subscriptions
Use the `subscribedWith(...)` assertion to compare subscriptions
```typescript
import { expect } from 'chai'
describe.marbles('simple example', ({ cold }) => {
it('emits and completes', () => {
const example$ = cold('--a|')
const expected = '--a|'
const expectedSub = '^--!'
expect(example$).to.equal(expected)
expect(example$).to.have.been.subscribedWith(expectedSub)
})
})
```
#### Controlling Subscriptions
Use the `subscription` language chain to provide finer control of subscription behavior during
the test run.
```typescript
import { expect } from 'chai'
describe.marbles('simple example', ({ hot }) => {
it('emits and completes', () => {
const example$ = hot('--a--a--a|')
const sub = '-----^---!'
const expected = '-----a--a|'
expect(example$).with.subscription(sub).to.equal(expected)
})
})
```
### Utilities
#### marbleValues
Use the `marbleValues` helper function to generate typed marble values objects to use with the
`cold` and `hot` observable generators and `with.marbleValues(...)` language chain.
```typescript
// infers type to MarbleValues<string, 'a' | 'b'> // { a: string, b: string }
const myMarbles = marbleValues({ a: 'foo', b: 'bar' })
```
**boolean marble values**
The `booleanMarbles` object includes marble values for working with boolean values.
```typescript
export const booleanMarbles = marbleValues({
f: false,
t: true,
u: undefined,
})
```