Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
jayphelps committed Mar 2, 2017
1 parent 3ae821c commit 1ac1d0e
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 40 deletions.
3 changes: 2 additions & 1 deletion docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
* [Recipes](recipes/SUMMARY.md)
* [Cancellation](recipes/Cancellation.md)
* [Error Handling](recipes/ErrorHandling.md)
* [Injecting Dependencies Into Epics](recipes/InjectingDependenciesIntoEpics.md)
* [Writing Tests](recipes/WritingTests.md)
* [Usage with UI Frameworks](recipes/UsageWithUIFrameworks.md)
* [Hot Module Replacement](recipes/HotModuleReplacement.md)
* [Adding New Epics Asynchronously](recipes/AddingNewEpicsAsynchronously.md)
* [Hot Module Replacement](recipes/HotModuleReplacement.md)
* [FAQ](FAQ.md)
* [Troubleshooting](Troubleshooting.md)
* [API Reference](api/SUMMARY.md)
Expand Down
6 changes: 3 additions & 3 deletions docs/api/createEpicMiddleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
1. *`rootEpic: Epic`*: The root [Epic](../basics/Epics.md)
2. *`[options: Object]`*: The optional configuration. Options:
* *`dependencies`*: If given, it will be injected as the 3rd argument to all epics.
* *`adapter`*: An adapter object which can transform the input / output `Observable`s. Options:
* *`input: ActionsObservable => Observable`*: Transforms the input `Observable` (transformation takes place *before* it is passed to the root epic).
* *`output: Observable => Observable`*: Transforms the output `Observable` (transformation takes place *after* the root epic returned it).
* *`adapter`*: An adapter object which can transform the input / output streams provided to your epics. Usually used to adapt a stream library other than RxJS v5, like [adapter-rxjs-v4](https://github.com/redux-observable/redux-observable-adapter-rxjs-v4) or [adapter-most](https://github.com/redux-observable/redux-observable-adapter-most) Options:
* *`input: ActionsObservable => any`*: Transforms the input stream of actions, `ActionsObservable` that is passed to your root Epic (transformation takes place *before* it is passed to the root epic).
* *`output: any => Observable`*: Transforms the return value of root Epic (transformation takes place *after* the root epic returned it).

#### Returns

Expand Down
92 changes: 58 additions & 34 deletions docs/recipes/InjectingDependenciesIntoEpics.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,79 @@
# Injecting Dependencies Into Epics

Sometimes you might want to have some dependencies injected into your epics. Things that you might want to mock in tests fall into this category.
Injecting your dependencies into your Epics can help with testing.

Let's say you want to interact with the network. You could use the `ajax` method directly from `rxjs`:
Let's say you want to interact with the network. You could use the `ajax` helpers directly from `rxjs`:

```js
import { ajax } from 'rxjs/observable/dom/ajax'

export const fetchUsersEpic = action$ => action$.ofType('FETCH_USERS_REQUESTED')
.flatMap(() => ajax.getJSON('https://my.api.com/users'))
.map(response => ({ type: 'FETCH_USERS_COMPLETED', payload: response }))
import { ajax } from 'rxjs/observable/dom/ajax';

const fetchUserEpic = (action$, store) =>
action$.ofType('FETCH_USER')
.mergeMap(({ payload }) =>
ajax.getJSON(`/api/users/${payload}`)
.map(response => ({
type: 'FETCH_USER_FULFILLED',
payload: response
}))
);
```

But there is a problem with this approach: how interacting with the network happens is now hardcoded into your epic. There is no way of mocking the `ajax.getJSON` method, and test if the _epic_ is actually doing what it should (without involving direct interaction with the API)!
But there is a problem with this approach: Your file containing the epic imports its dependency directly, so mocking it is much more difficult.

One approach might be to mock `window.XMLHttpRequest`, but this is a lot more work and now you're not just testing your Epic, you're testing that RxJS correctly uses XMLHttpRequest a certain way when in fact that shouldn't be the goal of your test.

### injecting dependencies
### Injecting dependencies

To inject dependencies you can use `createEpicMiddleware`'s `dependencies` configuration option:

```js
import { createEpicMiddleware, combineEpics } from 'redux-observable'
import { ajax } from 'rxjs/observable/dom/ajax'
import { fetchUsersEpic } from './fetchUsers'

export const epicMiddleware = createEpicMiddleware(
combineEpics(
fetchUsersEpic,
// other epics ...
),
{ dependencies: { ajax } }
)
```

With this in mind you can rewrite your epic without importing and using `ajax` directly, and just rely on the given `dependencies` object:
import { createEpicMiddleware, combineEpics } from 'redux-observable';
import { ajax } from 'rxjs/observable/dom/ajax';
import rootEpic from './somewhere';

```js
export const fetchUsersEpic = (action$, _ /* store */, { ajax }) => action$.ofType('FETCH_USERS_REQUESTED')
.flatMap(() => ajax.getJSON('https://my.api.com'))
.map(response => ({ type: 'FETCH_USERS_COMPLETED', payload: response }))
const epicMiddleware = createEpicMiddleware(rootEpic, {
dependencies: { getJSON: ajax.getJSON }
});
```

And in your test you can easily mock `ajax`:
Anything you provide will then be passed as the third argument to all your Epics, after the store.

Now your Epic can use the injected `getJSON`, instead of importing it itself:

```js
import { fetchUsersEpic } from './fetchUsers'
// Notice the third argument is our injected dependencies!
const fetchUserEpic = (action$, store, { getJSON }) =>
action$.ofType('FETCH_USER')
.mergeMap(() =>
getJSON(`/api/users/${payload}`)
.map(response => ({
type: 'FETCH_USER_FULFILLED',
payload: response
}))
);

const store = ...
const mockAjax = ...
const action = { type: 'FETCH_USERS_REQUESTED' }
```

const resultObservable = fetchUsersEpic(Observable.of(action), store, { ajax: mockAjax })
To test, you can just call your Epic directly, passing in a mock for `getJSON`:

// assertions
```js
import { ActionsObservable } from 'redux-observable';
import { fetchUserEpic } from './somewhere/fetchUserEpic';

const mockResponse = { name: 'Bilbo Baggins' };
const action$ = ActionsObservable.of({ type: 'FETCH_USERS_REQUESTED' });
const store = null; // not needed for this epic
const dependencies = {
getJSON: url => Observable.of(mockResponse)
};

// Adapt this example to your test framework and specific use cases
fetchUserEpic(action$, store, dependencies)
.toArray() // buffers all emitted actions until your Epic naturally completes()
.subscribe(actions => {
assertDeepEqual(actions, [{
type: 'FETCH_USER_FULFILLED',
payload: mockResponse
}]);
});
```
4 changes: 2 additions & 2 deletions docs/recipes/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

* [Cancellation](Cancellation.md)
* [Error Handling](ErrorHandling.md)
* [Injecting Dependencies Into Epics](InjectingDependenciesIntoEpics.md)
* [Writing Tests](WritingTests.md)
* [Usage with UI Frameworks](UsageWithUIFrameworks.md)
* [Hot Module Replacement](HotModuleReplacement.md)
* [Adding New Epics Asynchronously](AddingNewEpicsAsynchronously.md)
* [Injecting Dependencies Into Epics](InjectingDependenciesIntoEpics.md)
* [Hot Module Replacement](HotModuleReplacement.md)

Have a common pattern you can share? We would love it if you shared it with us! [Add your own Recipe](https://github.com/redux-observable/redux-observable/edit/master/docs/recipes/SUMMARY.md) or [create an issue](https://github.com/redux-observable/redux-observable/issues/new) with the examples.

0 comments on commit 1ac1d0e

Please sign in to comment.