• Stars
    star
    1,407
  • Rank 32,161 (Top 0.7 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 8 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A minimal test double library for TDD with JavaScript

testdouble.js (AKA td.js)

npmjs unpkg

Welcome! Are you writing JavaScript tests and in the market for a mocking library to fake out real things for you? testdouble.js is an opinionated, carefully-designed test double library maintained by, oddly enough, a software agency that's also named Test Double. (The term "test double" was coined by Gerard Meszaros in his book xUnit Test Patterns.)

If you practice test-driven development, testdouble.js was designed to promote terse, clear, and easy-to-understand tests. There's an awful lot to cover, so please take some time and enjoy our documentation, which is designed to show you how to make the most out of test doubles in your tests.

This library was designed to work for both Node.js and browser interpeters. It's also test-framework agnostic, so you can plop it into a codebase using Jasmine, Mocha, Tape, Jest, or our own teenytest.

Install

$ npm install -D testdouble

If you just want to fetch the browser distribution, you can also curl it from unpkg.

We recommend requiring the library in a test helper and setting it globally for convenience to the shorthand td:

// ES import syntax
import * as td from 'testdouble'

// CommonJS modules (e.g. Node.js)
globalThis.td = require('testdouble')

// Global set in our browser distribution
window.td

(You may need to configure your linter to ignore the td global. Instructions: eslint, standard.)

If you're using testdouble.js in conjunction with another test framework, you may also want to check out one of these extensions:

Getting started

Mocking libraries are more often abused than used effectively, so figuring out how to document a mocking library so as to only encourage healthy uses has proven to be a real challenge. Here are a few paths we've prepared for getting started with testdouble.js:

Of course, if you're unsure of how to approach writing an isolated test with testdouble.js, we welcome you to open an issue on GitHub to ask a question.

API

td.replace() and td.replaceEsm() for replacing dependencies

The first thing a test double library needs to do is give you a way to replace the production dependencies of your subject under test with fake ones controlled by your test.

We provide a top-level function called td.replace() that operates in two different modes: CommonJS module replacement and object-property replacement. Both modes will, by default, perform a deep clone of the real dependency which replaces all functions it encounters with fake test double functions which can, in turn, be configured by your test to either stub responses or assert invocations.

Additionally, if you're using Node 13 or newer, you can specify testdouble as a module loader and replace native ES modules with td.replaceEsm(). More details here.

Module replacement with Node.js

td.replace('../path/to/module'[, customReplacement])

If you're using Node.js and don't mind using the CommonJS require() function in your tests (you can still use import/export in your production code, assuming you're compiling it down for consumption by your tests), testdouble.js uses a library we wrote called quibble to monkey-patch require() so that your subject will automatically receive your faked dependencies simply by requiring them. This approach may be familiar if you've used something like proxyquire, but our focus was to enable an even more minimal test setup.

Here's an example of using td.replace() in a Node.js test's setup:

let loadsPurchases, generatesInvoice, sendsInvoice, subject
module.exports = {
  beforeEach: () => {
    loadsPurchases = td.replace('../src/loads-purchases')
    generatesInvoice = td.replace('../src/generates-invoice')
    sendsInvoice = td.replace('../src/sends-invoice')
    subject = require('../src/index')
  }
  //…
  afterEach: function () { td.reset() }
}

In the above example, at the point when src/index is required, the module cache will be bypassed as index is loaded. If index goes on to subsequently require any of the td.replace()'d dependencies, it will receive a reference to the same fake dependencies that were returned to the test.

Because td.replace() first loads the actual file, it will do its best to return a fake that is shaped just like the real thing. That means that if loads-purchases exports a function, a test double function will be created and returned. If generates-invoice exports a constructor, a constructor test double will be returned, complete with test doubles for all of the original's static functions and instance methods. If sends-invoice exports a plain object of function properties, an object will be returned with test double functions in place of the originals' function properties. In every case, any non-function properties will be deep-cloned.

There are a few important things to keep in mind about replacing Node.js modules using td.replace():

  • The test must td.replace() and require() everything in a before-each hook, in order to bypass the Node.js module cache and to avoid pollution between tests
  • Any relative paths passed to td.replace() are relative from the test to the dependency. This runs counter to how some other tools do it, but we feel it makes more sense
  • The test suite (usually in a global after-each hook) must call td.reset() to ensure the real require() function and dependency modules are restored after each test case.
Default exports with ES modules

If your modules are written in the ES module syntax and they specify default exports (e.g. export default function loadsPurchases()), but are actually transpiled to CommonJS, just remember that you'll need to reference .default when translating to the CJS module format.

That means instead of this:

loadsPurchases = td.replace('../src/loads-purchases')

You probably want to assign the fake like this:

loadsPurchases = td.replace('../src/loads-purchases').default

Property replacement

td.replace(containingObject, nameOfPropertyToReplace[, customReplacement])

If you're running tests outside Node.js or otherwise injecting dependencies manually (or with a DI tool like dependable), then you may still use td.replace to automatically replace things if they're referenceable as properties on an object.

To illustrate, suppose our subject depends on app.signup below:

app.signup = {
  onSubmit: function () {},
  onCancel: function () {}
}

If our goal is to replace app.signup during a test of app.user.create(), our test setup might look like this:

let signup, subject
module.exports = {
  beforeEach: function () {
    signup = td.replace(app, 'signup')
    subject = app.user
  }
  // …
  afterEach: function () { td.reset() }
}

td.replace() will always return the newly-created fake imitation, even though in this case it's obviously still referenceable by the test and subject alike with app.signup. If we had wanted to only replace the onCancel function for whatever reason (though in this case, that would smell like a partial mock), we could have called td.replace(app.signup, 'onCancel'), instead.

Remember to call td.reset() in an after-each hook (preferably globally so one doesn't have to remember to do so in each and every test) so that testdouble.js can replace the original. This is crucial to avoiding hard-to-debug test pollution!

Specifying a custom replacement

The library's imitation feature is pretty sophisticated, but it's not perfect. It's also going to be pretty slow on large, complex objects. If you'd like to specify exactly what to replace a real dependency with, you can do so in either of the above modes by providing a final optional argument.

When replacing a Node.js module:

generatesInvoice = td.replace('../generates-invoice', {
  generate: td.func('a generate function'),
  name: 'fake invoices'
})

When replacing a property:

signup = td.replace(app, 'signup', {
  onSubmit: td.func('fake submit handler'),
  onCancel: function () { throw Error('do not call me') }
})

td.func(), td.object(), td.constructor(), td.instance() and td.imitate() to create test doubles

td.replace()'s imitation and injection convenience is great when your project's build configuration allows for it, but in many cases you'll want or need the control to create fake things directly. Each creation function can either imitate a real thing or be specified by passing a bit of configuration.

Each test double creation function is very flexible and can take a variety of inputs. What gets returned generally depends on the number and type of configuration parameters passed in, so we'll highlight each supported usage separately with an example invocation:

td.func()

The td.func() function (also available as td.function()) returns a test double function and can be called in three modes:

  • td.func(someRealFunction) - returns a test double function of the same name, including a deep imitation of all of its custom properties
  • td.func() - returns an anonymous test double function that can be used for stubbing and verifying any calls against it, but whose error messages and debugging output won't have a name to trace back to it
  • td.func('some name') - returns a test double function named 'some name', which will appear in any error messages as well as the debug info returned by passing the returned test double into td.explain()
  • td.func<Type>() - returns a test double function imitating the passed type. Examples and more details can be found in using with TypeScript

td.object()

The td.object() function returns an object containing test double functions, and supports three types of invocations:

  • td.object(realObject) - returns a deep imitation of the passed object, where each function is replaced with a test double function named for the property path (e.g. If realObject.invoices.send() was a function, the returned object would have property invoices.send set to a test double named '.invoices.send')
  • td.object(['add', 'subtract']) - returns a plain JavaScript object containing two properties add and subtract that are both assigned to test double functions named '.add' and '.subtract', respectively
  • td.object('a Person'[, {excludeMethods: ['then']}) - when passed with no args or with a string name as the first argument, returns an ES Proxy. The proxy will automatically intercept any call made to it and shunt in a test double that can be used for stubbing or verification. More details can be found in our full docs
  • td.object<Interface>() - returns an object with methods exposed as test doubles that are typed according to the passed interface. Examples and more details can be found in using with TypeScript

td.constructor()

If your code depends on ES classes or functions intended to be called with new, then the td.constructor() function can replace those dependencies as well.

  • td.constructor(RealConstructor) - returns a constructor whose calls can be verified and whose static and prototype functions have all been replaced with test double functions using the same imitation mechanism as td.func(realFunction) and td.object(realObject)
  • td.constructor(['select', 'save']) - returns a constructor with select and save properties on its prototype object set to test double functions named '#select' and '#save', respectively

When replacing a constructor, typically the test will configure stubbing & verification by directly addressing its prototype functions. To illustrate, that means in your test you might write:

const FakeConstructor = td.constructor(RealConstructor)
td.when(FakeConstructor.prototype.doStuff()).thenReturn('ok')

subject(FakeConstructor)

So that in your production code you can:

const subject = function (SomeConstructor) {
  const thing = new SomeConstructor()
  return thing.doStuff() // returns "ok"
}

td.instance()

As a shorthand convenience, td.instance() function will call td.constructor() and return a new instance of the fake constructor function it returns.

The following code snippets are functionally equivalent:

const fakeObject = td.instance(RealConstructor)
const FakeConstructor = td.constructor(RealConstructor)
const fakeObject = new FakeConstructor()

td.imitate()

td.imitate(realThing[, name])

If you know you want to imitate something, but don't know (or care) whether it's a function, object, or constructor, you can also just pass it to td.imitate() with an optional name parameter.

td.when() for stubbing responses

td.when(__rehearsal__[, options])

Once you have your subject's dependencies replaced with test double functions, you'll want to be able to stub return values (and other sorts of responses) when the subject invokes the test double in the way that the test expects.

To make stubbing configuration easy to read and grep, td.when()'s first argument isn't an argument at all, but rather a placeholder to demonstrate the way you're expecting the test double to be invoked by the subject, like so:

const increment = td.func()
td.when(increment(5)).thenReturn(6)

We would say that increment(5) is "rehearsing the invocation". Note that by default, a stubbing is only satisfied when the subject calls the test double exactly as it was rehearsed. This can be customized with argument matchers, which allow for rehearsals that do things like increment(td.matchers.isA(Number)) or save(td.matchers.contains({age: 21})).

Also note that, td.when() takes an optional configuration object as a second parameter, which enables advanced usage like ignoring extraneous arguments and limiting the number of times a stubbing can be satisfied.

Calling td.when() returns a number of functions that allow you to specify your desired outcome when the test double is invoked as demonstrated by your rehearsal. We'll begin with the most common of these: thenReturn.

td.when().thenReturn()

td.when(__rehearsal__[, options]).thenReturn('some value'[, more, values])

The simplest example is when you want to return a specific value in exchange for a known argument, like so:

const loadsPurchases = td.replace('../src/loads-purchases')
td.when(loadsPurchases(2018, 8)).thenReturn(['a purchase', 'another'])

Then, in the hands of your subject under test:

loadsPurchases(2018, 8) // returns `['a purchase', 'another']`
loadsPurchases(2018, 7) // returns undefined, since no stubbing was satisfied

If you're not used to stubbing, it may seem contrived to think a test will know exactly what argument to pass in and expect back from a dependency, but in an isolated unit test this is not only feasible but entirely normal and expected! Doing so helps the author ensure the test remains minimal and obvious to future readers.

Note as well that subsequent matching invocations can be stubbed by passing additional arguments to thenReturn(), like this:

const hitCounter = td.func()
td.when(hitCounter()).thenReturn(1, 2, 3, 4)

hitCounter() // 1
hitCounter() // 2
hitCounter() // 3
hitCounter() // 4
hitCounter() // 4

td.when().thenResolve() and td.when().thenReject()

td.when(__rehearsal__[, options]).thenResolve('some value'[, more, values])

td.when(__rehearsal__[, options]).thenReject('some value'[, more, values])

The thenResolve() and thenReject() stubbings will take whatever value is passed to them and wrap it in an immediately resolved or rejected promise, respectively. By default testdouble.js will use whatever Promise is globally defined, but you can specify your own like this:

td.config({promiseConstructor: require('bluebird')})`

Because the Promise spec indicates that all promises must tick the event loop, keep in mind that any stubbing configured with thenResolve or thenReject must be managed as an asynchronous test (consult your test framework's documentation if you're not sure).

td.when().thenCallback()

td.when(__rehearsal__[, options]).thenCallback('some value'[,other, args])

The thenCallback() stubbing will assume that the rehearsed invocation has an additional final argument that takes a callback function. When this stubbing is satisfied, testdouble.js will invoke that callback function and pass in whatever arguments were sent to thenCallback().

To illustrate, consider this stubbing:

const readFile = td.replace('../src/read-file')
td.when(readFile('my-secret-doc.txt')).thenCallback(null, 'secrets!')

Then, the subject might invoke readFile and pass an anonymous function:

readFile('my-secret-doc.txt', function (err, contents) {
  console.log(contents) // will print 'secrets!'
})

If the callback isn't in the final position, or if the test double also needs to return something, callbacks can be configured using the td.callback argument matcher.

On one hand, thenCallback() can be a great way to write fast and clear synchronous isolated unit tests of production code that's actually asynchronous. On the other hand, if it's necessary to verify the subject behaves correctly over multiple ticks of the event loop, you can control this with the defer and delay options.

td.when().thenThrow()

td.when(__rehearsal__[, options]).thenThrow(new Error('boom'))

The thenThrow() function does exactly what it says on the tin. Once this stubbing is configured, any matching invocations will throw the specified error.

Note that because rehearsal calls invoke the test double function, it's possible to configure a thenThrow stubbing and then accidentally trigger it when you attempt to configure subsequent stubbings or verifications. In these cases, you'll need to work around it by re-ordering your configurations or catch'ing the error.

td.when().thenDo()

td.when(__rehearsal__[, options]).thenDo(function (arg1, arg2) {})

For everything else, there is thenDo(). thenDo takes a function which will be invoked whenever satisfied with all the arguments and bound to the same this context that the test double function was actually invoked with. Whatever your thenDo function returns will be returned by the test double when the stubbing is satisfied. This configuration is useful for covering tricky cases not handled elsewhere, and may be a potential extension point for building on top of the library's stubbing capabilities.

td.verify() for verifying interactions

td.verify(__demonstration__[, options])

If you've learned how to stub responses with td.when() then you already know how to verify an invocation took place with td.verify()! We've gone out of our way to make the two as symmetrical as possible. You'll find that they have matching function signatures, support the same argument matchers, and take the same options.

The difference, then, is their purpose. While stubbings are meant to facilitate some behavior we want to exercise in our subject, verifications are meant to ensure a dependency was called in a particular expected way. Since td.verify() is an assertion step, it goes at the end of our test after we've invoked the subject under test.

A trivial example might be:

module.exports = function shouldSaveThings () {
  const save = td.replace('../src/save')
  const subject = require('../src/index')

  subject({name: 'dataz', data: '010101'})

  td.verify(save('dataz', '010101'))
}

The above will verify that save was called with the two specified arguments. If the verification fails (say it passed '010100' instead), testdouble.js will throw a nice long error message to explain how the test double function was actually called, hopefully helping you spot the error.

Just like with td.when(), more complex cases can be covered with argument matchers and configuration options.

A word of caution: td.verify() should be needed only sparingly. When you verify a function was called (as opposed to relying on what it returns) you're asserting that your subject has a side effect. Code with lots of side effects is bad, so mocking libraries are often abused to make side-effect heavy code easier to proliferate. In these cases, refactoring each dependency to return values instead is almost always the better design approach. A separate test smell with verifying calls is that sometimesβ€”perhaps in the interest of maximal completenessβ€”a test will verify an invocation that already satisfied a stubbing, but this is almost provably unnecessary.

Other functions

For other top-level features in the testdouble.js API, consult the docs directory:

More Repositories

1

standard

🌟 Ruby Style Guide, with linter & automatic code fixer
Ruby
2,104
star
2

suture

πŸ₯ A Ruby gem that helps you refactor your legacy code
Ruby
1,401
star
3

contributing-tests

1,104
star
4

scripty

Because no one should be shell-scripting inside a JSON file.
JavaScript
957
star
5

test-smells

A workbook repository of example test smells and what to do about them.
JavaScript
421
star
6

jasmine-rails

A Jasmine runner for rails projects that's got you covered in both the terminal and the browser
JavaScript
378
star
7

referral

πŸ•΅οΈβ€β™€οΈ Find, filter, and sort your Ruby code's definitions & references
Ruby
343
star
8

cypress-rails

Helps you write Cypress tests of your Rails app
Ruby
312
star
9

good-migrations

Prevent Rails from auto-loading app/ code when running database migrations
Ruby
294
star
10

mocktail

πŸ₯ƒ Take your Ruby, and make it a double!
Ruby
273
star
11

static-rails

Build & serve static sites (e.g. Jekyll, Hugo) from your Rails app
Ruby
149
star
12

maybe_later

Run code after the current Rack response or Rails action completes
Ruby
132
star
13

time_up

⏱ Create and manage multiple timers to tell where your Ruby code's time is going
Ruby
117
star
14

teenytest

A very simple, zero-config test runner for Node.js
JavaScript
97
star
15

test_data

A fast & reliable system for managing your Rails application's test data
Ruby
95
star
16

put

Ruby
92
star
17

theredoc

Makes your multi-line JavaScript strings look good
JavaScript
79
star
18

quibble

Makes it easy to replace require'd dependencies.
JavaScript
78
star
19

react-decoupler

JavaScript
55
star
20

noncommittal

A gem that ensures test isolation by preventing your Rails tests from committing to the database
Ruby
46
star
21

real-world-testing-video

testdouble/real-world-testing + screencasts
JavaScript
40
star
22

clojurescript.csv

A ClojureScript library for reading and writing CSV
Clojure
37
star
23

testdouble-jest

A testdouble.js extension to add support for Jest module mocking
JavaScript
37
star
24

grunt-markdown-blog

Grunt task for building a blog with markdown posts & underscore templates
CoffeeScript
36
star
25

ought

A dumb assertion library with smart diffs for JavaScript
JavaScript
34
star
26

cypress-capybara

Capybara finders re-implemented as custom Cypress commands
JavaScript
33
star
27

minitest-suite

Re-order your Minitest suite into logical sub-suites/groups
Ruby
32
star
28

rust-ffi-example

An example project that shows how to use FFI between Rust and Unity.
Rust
31
star
29

gem_dating

How old is that anyway?
Ruby
29
star
30

rspec-graphql_response

Verify ruby-graphql responses with a :graphql spec type
Ruby
25
star
31

ecto_resource

A simple module to clear up the boilerplate of CRUD resources in Phoenix context files.
Elixir
22
star
32

java-testing-example

An example project that's configured for JUnit and Mocha
Java
20
star
33

real-world-testing

Workshop for Testing JavaScripts
JavaScript
17
star
34

unusual-spending

A code kata for outside-in TDD in Node.js
JavaScript
16
star
35

webpacker-assets-demo

A demo repo to show how to reference images and styles when using Webpacker instead of Sprockets
Ruby
13
star
36

javascript-testing-tactics

The Missing Manual for writing great JavaScript Testing
13
star
37

magic_email_demo

An exampleΒ Rails app that implements passwordless authentication by emailing a magic link
Ruby
12
star
38

scheduled-merge

Merge PRs on a specified date using Labels
JavaScript
12
star
39

rust-ffi-complex-example

Follow-up project to shows how to use complex data structures between Unity and Rust.
Rust
12
star
40

todos

JavaScript
11
star
41

grunt-asset-fingerprint

CoffeeScript
9
star
42

covet

Instruct a remote Express app to stub APIs via HTTP requests
CoffeeScript
9
star
43

bored

Gives you ideas of stuff to do when you're bored
Ruby
8
star
44

rails-twitter-oauth-example

An example Rails app that implements log in to Twitter via OAuth
Ruby
8
star
45

javascript-tdd-examples

Yet another little toy repo of javascript tdd examples
JavaScript
8
star
46

baizen

BAI file format parser
Clojure
8
star
47

tiny_type

Fast, easy, and simple runtime type checking for Ruby
Ruby
8
star
48

halfpipe

A Pipedrive client for Ruby that doesn't do half of what you want it to πŸ›Ή
Ruby
7
star
49

forewarn

Configure method invocation warnings for deprecated or dangerous methods (e.g. mutable methods in default-frozen String literals in Ruby 3)
Ruby
7
star
50

grunt-jasmine-bundle

A "spec" grunt task for Jasmine that includes a standard pack of helpers (jasmine-given, jasmine-stealth, jasmine-only). Uses minijasminenode.
CoffeeScript
6
star
51

servme

gimme for integration tests
Ruby
6
star
52

intro-to-node

Introduction to Node.js Workshop
JavaScript
6
star
53

standardrb

You're probably in the wrong place. This is an alias for the gem standard, whose binary is standardrb
Ruby
6
star
54

bootboot-example

An example of using boot-boot.
Ruby
5
star
55

testdrivennode

Test Driven Node.js Precompiler for Codemash 2014
JavaScript
5
star
56

docunit

Makes sure the code examples in your docs actually work
CoffeeScript
5
star
57

railsconf-test-drive-javascript

JavaScript
5
star
58

json-to-svg-to-pdf

Converts JSON/CSON input through SVG templates and renders them to PDF using librsvg
JavaScript
5
star
59

imagemagick-macos-font-setup

Sets up user fonts for imagemagick on macOS
Shell
5
star
60

jasmine-before-all

Adds a done-friendly beforeAll global function to Jasmine
JavaScript
5
star
61

good-day

An example ember + active_model_serializers + rails + lineman app
JavaScript
5
star
62

sockem

A wrapper around the ActionCable JS client to ensure eventual delivery for requests
Ruby
5
star
63

satisfaction

Satisfaction tracker for your work!
Ruby
5
star
64

headerify

Browserify plugin to add a comment containing lib name, version, description, and homepage to the top of the bundle
JavaScript
4
star
65

SublimeLinter-contrib-standardrb

SublimeLinter 3 plugin for Ruby, using Standard, a wrapper for Rubocop.
Python
4
star
66

rails-upsert-all-demo

An example app that demos use of Rails 6 `upsert_all` method
Ruby
4
star
67

cobbler

A tool to generate rΓ©sumΓ©s for Test Double agents.
JavaScript
3
star
68

react-d3-blog-example

Example for Blog Post
JavaScript
3
star
69

supertitle

Converts between subtitles and transcript formats
Ruby
3
star
70

time_traveler_demo

A Rails app that demoes time traveling both Ruby and Postgres in lock-step with one another
Ruby
3
star
71

devops-standards

Standard Auditing Tools for DevSecOps best practices
Python
3
star
72

least

A pager that can dynamically filter log lines
Ruby
3
star
73

defuse

An API to define and use JavaScript in a module-y way. And nothing else.
JavaScript
3
star
74

lockify

Ensure an async function does not run concurrently.
JavaScript
3
star
75

jasmine-matcher-wrapper

A utility to wrap Jasmine 1.x argument matchers for use under Jasmine 2.x
CoffeeScript
3
star
76

testdouble-nock

JavaScript
3
star
77

teenytest-promise

Promise support for asynchronous teenytest tests
JavaScript
3
star
78

npm-tree

Generates a tree of all the node.js modules depended on by a module
CoffeeScript
3
star
79

function-names-at-line

Name the functions found at a particular line number in some JavaScript source
JavaScript
2
star
80

tradecraft

CSS
2
star
81

course-cypress-intro-demo-app

Demo application to supplement Test Double's End-to-end Testing with Cypress intro video course
Ruby
2
star
82

standard-ruby-action

Ruby
2
star
83

rails-training-201

A demo app for Rails 201 students to build on!
Ruby
2
star
84

testdrivennode-frontend

JavaScript
2
star
85

yslow-grader

A little Node.js wrapper for YSlow for PhantomJS
CoffeeScript
2
star
86

ios-learnins

Objective-C
2
star
87

fetcher

Fetches things based on a JSON recipe hosted in a repository
CoffeeScript
2
star
88

backbone-fixins

Boilerplate that strengthens your backbone
JavaScript
2
star
89

ruby_rails_training_github

Ruby
1
star
90

prioritize-api

Elixir
1
star
91

baruco2014-angular

Ruby
1
star
92

oredev2014-angular

JavaScript
1
star
93

double-up

Slack scheduler to set up rotating brunch pairings
Ruby
1
star
94

elm-testdouble

A minimal test double library for TDD with Elm
Elm
1
star
95

doubot

test double's hubot
CoffeeScript
1
star
96

jasmine-example

JavaScript
1
star
97

arg-that

arg-that makes it easier to assert equality on complex objects
Ruby
1
star
98

react-routing-example

Example for screencast on client-side routing in React
CSS
1
star
99

cucumber-peel

Provides a CLI to search a project's step implementations for a given step
Ruby
1
star
100

test_rails_app

A starter Rails application to test your environment setup
Ruby
1
star