npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-snoop

v0.0.1

Published

_Grey-box testing for redux flow_

Downloads

2

Readme

redux-snoop 🕵️

Grey-box testing for redux flow

Introduction

Testing redux flow (beyond unit tests) requires the test not only to have knowledge of internal implementation of store creation, but also to alter it. This library introduces more of a grey-box approach and let you write tests even if you don't have or want access to the actual store creation logic.

Why redux-snoop?

  1. Doesn't alter your code - snoop will attach itself to any existing redux store, meaning your test code and production code will behave the same.
  2. Clean promise based syntax when dealing with complex async flow.

Why not redux-snoop?

This library patches redux methods. It doesn't do anything that you don't already do with jest-mock but it might not be suitable to everyone.

Install

yarn install redux-snoop

Usage

You can use redux-snoop in 2 ways, first method is for simple stores and the second is for stores with middleware such as redux-saga

A deeper comparison will follow in the coming section.

1. For testing stores without middleware

import { ReduxSnoop } from 'redux-snoop';

const store = creatStore(...)

const snoop = new ReduxSnoop(store)

store.dispatch({type: "some-action", payload: "foo"})

const result = await snoop.waitForAction(anotherAction)

// result === { action: {type: "some-action", payload: "foo", state: {...} }

store.dispatch({type: "another-action", payload: "bar"})

const steps = snoop.getSteps();

// steps === [
// { action: { type: "some-action", payload: "foo" }, state: { ... } },
// { action: { type: "another-action", payload: "bar" }, state: { ... } }
// ];

snoop.reset();

// snoop.getSteps() === []

2. For stores with middleware that dispatch actions (ie redux-saga)


// Important! This must come at the top of your test suite!

jest.mock('redux', () => {
  const inject = require('redux-snoop').injectReduxSnoop;
  return inject();
})

describe('Test with inject', () => {

  it('Should add snoop to any store', async () => {

    const store = createStore(...);

    // Snoop is already attached to the store
    expect(store.snoop).toBeInstanceOf(ReduxSnoop);

    setTimeout(() => store.dispatch({ type: 'SOME_ACTION' }), 10);

    // Wait for the action
    const step = await store.snoop.waitForAction('SOME_ACTION');

    expect(store.snoop.getSteps()).toHaveLength(1)
    expect(step.action.type).tpEqual('SOME_ACTION');

    // Reset the store between tests
    store.snoop.reset();

    expect(store.snoop.getSteps()).toHaveLength(0)
  })
})

Description

Problems with Redux tests

There are many ways to test redux with unit tests, but when it comes to component testing, that is, testing a flow that includes reducers, actions and middleware, is more challenging. This is because Redux doesn't provide an outside interface that logs both actions and state. This limit is by design and prevents anti-pattern and abuse of the library.

This limitation forces us to be creative when writing our tests, common patterns are:

  1. Add reducer that acts as logger.
  2. Use a mock store library (as in redux-mock-store).

Both of the solutions are considered white-box tests and are problematic since they force us to change the mechanics of our just for tests, and so the tests don't reflect our production code.

Enter redux-snoop

This library uses grey-box testing approach and patches redux with minimalistic interceptors or wrappers during tests. This allows us to snoop on stores that were created by other parts in our code, without us needing to have access to them or manually change them.

redux-snoop introduces the concept of Step which is a step in the life-cycle of a store, consisting of an action and the state tree as it recorded after dispatch is done.

It will also allow you wait for a future or async actions using promises. This is extremely helpful when testing redux flow that involves multiple actions and async activity, like the one involved with redux-saga or thunk, making your tests more readable and clean.

Though it shouldn't affect the mechanics of the store, it does consider to be a hack and so it shouldn't be used outside of a test environment.

How does it work?

There are two ways to use snoop, depends on your needs:

  1. Intercept dispatch method - for common use.
  2. Patch createStore using jest-mocks - for use with middlewares that dispatch actions (redux-saga).

You may find an example for aach of the methods in the previous section

First Method: Intercepting dispatch()

redux-snoop intercepts dispatch by taking the original store's dispatch method and wraps it with it's own logic. Setup is simple as it only accepts an existing store object and its affects are limited to that object alone.

Second Method: Patch createStore() (only for Jest tests)

This method is more intrusive since it involves mocking redux library, but it is inevitable if your store is using middlewares. In that case, intercepting dispatch on the created store will not work since createStore will pass a different method reference to the middleware.

In that case we will use jest-mock to provide a wrapper around createStore that, upon creation of a new store, wraps the root reducer and logs its activity. It will add a property snoop to the store, which is a ReduxSnoop object that follows that store.

Note: in the first method you will manually create ReduxSnoop using its constructor, while with the second one you will already have an instances created and passes to you as part of the stores object.

ReduxSnoop Methods

constructor(store?: Store)

Creates a new instance of Snoop for the given store.

Please note that Store is any vanilla redux store as it returned by createStore. If you use any modification for redux or redux store creation, this library may not work correctly.

waitForAction(actionName: string | string[], skip: number = 0) : Promise<IStep>

Waits for an one or more actions and return a promise that resolves into a Step which is a tuple of the dispatched action object and the state as it appears after the dispatch.

actionName: string | string[]

One or more action types to wait for. If more than one actions are stated, then the "Or" Operator is applied and the promise is resolved when the either action is dispatched.

skip? : number

Skip the n number of occurrences of the action before resolving. Defaults for 0, meaning no skip.

Note: method will work the same even if waitFor is called after the action was dispatched. In that case it will return a promise that is immediately resolved.

getSteps():IStep[]

A Step is a tuple of action and state describing a dispatched action and the state of the store after dispatch is done. This method will return the sequence of steps as they were logged by snoop up to the time of the call.

reset()

Resets the steps log.

dispose()

Un-attach snoop from the store, restoring all interceptors to original values.