• Stars
    star
    273
  • Rank 145,418 (Top 3 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 2 years ago
  • Updated 2 months ago

Reviews

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

Repository Details

🥃 Take your Ruby, and make it a double!

Mocktail 🍸

Mocktail is a test double library for Ruby that provides a terse and robust API for creating mocks, getting them in the hands of the code you're testing, stub & verify behavior, and even safely override class methods.

If you'd prefer a voice & video introduction to Mocktail aside from this README, you might enjoy this ⚡️Lightning Talk⚡️ from RailsConf 2022.

An aperitif

Before getting into the details, let's demonstrate what Mocktail's API looks like. Suppose you want to test a Bartender class:

class Bartender
  def initialize
    @shaker = Shaker.new
    @glass = Glass.new
    @bar = Bar.new
  end

  def make_drink(name, customer:)
    if name == :negroni
      drink = @shaker.combine(:gin, :campari, :sweet_vermouth)
      @glass.pour!(drink)
      @bar.pass(@glass, to: customer)
    end
  end
end

You could write an isolated unit test with Mocktail like this:

shaker = Mocktail.of_next(Shaker)
glass = Mocktail.of_next(Glass)
bar = Mocktail.of_next(Bar)
subject = Bartender.new
stubs { shaker.combine(:gin, :campari, :sweet_vermouth) }.with { :a_drink }
stubs { bar.pass(glass, to: "Eileen") }.with { "🎉" }

result = subject.make_drink(:negroni, customer: "Eileen")

assert_equal "🎉", result
# Oh yeah, and make sure the drink got poured! Silly side effects!
verify { glass.pour!(:a_drink) }

Why Mocktail?

Besides helping you avoid a hangover, Mocktail offers several advantages over Ruby's other mocking libraries:

  • Simpler test recipes: Mocktail.of_next(type) both creates your mock and supplies to your subject under test in a single one-liner. No more forcing dependency injection for the sake of your tests
  • WYSIWYG API: Want to know how to stub a call to phone.dial(911)? You just demonstrate the call with stubs { phone.dial(911) }.with { :operator }. Because stubbing & verifying looks just like the actual call, your tests becomes a sounding board for your APIs as you invent them
  • Argument validation: Ever see a test pass after a change to a mocked method should have broken it? Not with Mocktail, you haven't
  • Mocked class methods: Singleton methods on modules and classes can be faked out using Mocktail.replace(type) without sacrificing thread safety
  • Super-duper detailed error messages A good mocking library should make coding feel like paint-by-number, thoughtfully guiding you from one step to the next. Calling a method that doesn't exist will print a sample definition you can copy-paste. Verification failures will print every call that did occur. And Mocktail.explain() provides even more introspection
  • Expressive: Built-in argument matchers and a simple API for adding custom matchers allow you to tune your stubbing configuration and call verification to match exactly what your test intends
  • Powerful: Argument captors for assertions of very complex arguments, as well as advanced configuration options for stubbing & verification

Ready to order?

Install the gem

The main ingredient to add to your Gemfile:

gem "mocktail", group: :test

Sprinkle in the DSL

Then, in each of your tests or in a test helper, you'll probably want to include Mocktail's DSL. (This is optional, however, as every method in the DSL is also available as a singleton method on Mocktail.)

In Minitest, you might add the DSL with:

class Minitest::Test
  include Mocktail::DSL
end

Or, in RSpec:

RSpec.configure do |config|
  config.include Mocktail::DSL
end

Clean up when you're done

To reset Mocktail's internal state between tests and avoid test pollution, you should also call Mocktail.reset after each test:

In Minitest:

class Minitest::Test
  # Or, if in a Rails test, in a `teardown do…end` block
  def teardown
    Mocktail.reset
  end
end

And RSpec:

RSpec.configure do |config|
  config.after(:each) do
    Mocktail.reset
  end
end

API

The entire public API is listed in the top-level module's source. Below is a longer menu to explain what goes into each feature.

Mocktail.of

Mocktail.of(module_or_class) takes a module or class and returns an instance of an object with fake methods in place of all its instance methods which can then be stubbed or verified.

class Clothes; end;
class Shoe < Clothes
  def tie(laces)
  end
end

shoe = Mocktail.of(Shoe)
shoe.instance_of?(Shoe) # => true
shoe.is_a?(Clothes) # => true
shoe.class == Shoe # => false!
shoe.to_s # => #<Mocktail of Shoe:0x00000001343b57b0>"

Mocktail.of_next

Mocktail.of_next(klass, [count: 1]) takes a class and returns one mock (the default) or an array of multiple mocks. It also effectively overrides the behavior of that class's constructor to return those mock(s) in order and finally restoring its previous behavior.

For example, if you wanted to test the Notifier class below:

class Notifier
  def initialize
    @mailer = Mailer.new
  end

  def notify(name)
    @mailer.deliver!("Hello, #{name}")
  end
end

You could write a test like this:

def test_notifier
  mailer = Mocktail.of_next(Mailer)
  subject = Notifier.new

  subject.notify("Pants")

  verify { mailer.deliver!("Hello, Pants") }
end

There's nothing wrong with creating mocks using Mocktail.of and passing them to your subject some other way, but this approach allows you to write very terse isolation tests without foisting additional indirection or dependency injection in for your tests' sake.

Mocktail.stubs

Configuring a fake method to take a certain action or return a particular value is called "stubbing". To stub a call with a value, you can call Mocktail.stubs (or just stubs if you've included Mocktail::DSL) and then specify an effect that will be invoked whenever that call configuration is satisfied using with.

The API is very simple in the simple case:

class UserRepository
  def find(id, debug: false); end

  def transaction(&blk); end
end

You could stub responses to a mock of the UserRepository like this:

user_repository = Mocktail.of(UserRepository)

stubs { user_repository.find(42) }.with { :a_user }
user_repository.find(42) # => :a_user
user_repository.find(43) # => nil
user_repository.find # => ArgumentError: wrong number of arguments (given 0, expected 1)

The block passed to stubs is called the "demonstration", because it represents an example of the kind of calls that Mocktail should match.

If you want to get fancy, you can use matchers to make your demonstration more dynamic. For example, you could match any number with:

stubs { |m| user_repository.find(m.numeric) }.with { :another_user }
user_repository.find(41) # => :another_user
user_repository.find(42) # => :another_user
user_repository.find(43) # => :another_user

Stubbings are last-in-wins, which is why the stubbing above would have overridden the earlier-but-more-specific stubbing of find(42).

A stubbing's effect can also be changed dynamically based on the actual call that satisfied the demonstration by looking at the call block argument:

stubs { |m| user_repository.find(m.is_a(Integer)) }.with { |call|
  {id: call.args.first}
}
user_repository.find(41) # => {id: 41}
# Since 42.5 is a Float, the earlier stubbing will win here:
user_repository.find(42.5) # => :another_user
user_repository.find(43) # => {id: 43}

It's certainly more complex to think through, but if your stubbed method takes a block, your demonstration can pass a block of its own and inspect or invoke it:

stubs {
  user_repository.transaction { |block| block.call == {id: 41} }
}.with { :successful_transaction }

user_repository.transaction {
  user_repository.find(41)
} # => :successful_transaction
user_repository.transaction {
  user_repository.find(40)
} # => nil

There are also several advanced options you can pass to stubs to control its behavior.

times will limit the number of times a satisfied stubbing can have its effect:

stubs { |m| user_repository.find(m.any) }.with { :not_found }
stubs(times: 2) { |m| user_repository.find(1) }.with { :someone }

user_repository.find(1) # => :someone
user_repository.find(1) # => :someone
user_repository.find(1) # => :not_found

ignore_extra_args will allow a demonstration to be considered satisfied even if it fails to specify arguments and keyword arguments made by the actual call:

stubs { user_repository.find(4) }.with { :a_person }
user_repository.find(4, debug: true) # => nil

stubs(ignore_extra_args: true) { user_repository.find(4) }.with { :b_person }
user_repository.find(4, debug: true) # => :b_person

And ignore_block will similarly allow a demonstration to not concern itself with whether an actual call passed the method a block—it's satisfied either way:

stubs { user_repository.transaction }.with { :transaction }
user_repository.transaction {} # => nil

stubs(ignore_block: true) { user_repository.transaction }.with { :transaction }
user_repository.transaction {} # => :transaction

Mocktail.verify

In practice, we've found that we stub far more responses than we explicitly verify a particular call took place. That's because our code normally returns some observable value that is influenced by our dependencies' behavior, so adding additional assertions that they be called would be redundant. That said, for cases where a dependency doesn't return a value but just has a necessary side effect, the verify method exists (and like stubs is included in Mocktail::DSL).

Once you've gotten the hang of stubbing, you'll find that the verify method is intentionally very similar. They almost rhyme.

For this example, consider an Auditor class that our code might need to call to record that certain actions took place.

class Auditor
  def record!(message, user_id:, action: nil); end
end

Once you've created a mock of the Auditor, you can start verifying basic calls:

auditor = Mocktail.of(Auditor)

verify { auditor.record!("hello", user_id: 42) }
# => raised Mocktail::VerificationError
# Expected mocktail of Auditor#record! to be called like:
#
#   record!("hello", user_id: 42)
#
# But it was never called.

Wups! Verify will blow up whenever a matching call hasn't occurred, so it should be called after you've invoked your subject under test along with any other assertions you have.

If we make a call that satisfies the verify call's demonstration, however, you won't see that error:

auditor.record!("hello", user_id: 42)

verify { auditor.record!("hello", user_id: 42) } # => nil

There, nothing happened! Just like any other assertion library, you only hear from verify when verification fails.

Just like with stubs, you can any built-in or custom matchers can serve as garnishes for your demonstration:

auditor.record!("hello", user_id: 42)

verify { |m| auditor.record!(m.is_a(String), user_id: m.numeric) } # => nil
# But this will raise a VerificationError:
verify { |m| auditor.record!(m.is_a(String), user_id: m.that { |arg| arg > 50}) }

When you pass a block to your demonstration, it will be invoked with any block that was passed to the actual call to the mock. Truthy responses will satisfy the verification and falsey ones will fail:

auditor.record!("ok", user_id: 1) { Time.new }

verify { |m| auditor.record!("ok", user_id: 1) { |block| block.call.is_a?(Time) } } # => nil
# But this will raise a VerificationError:
verify { |m| auditor.record!("ok", user_id: 1) { |block| block.call.is_a?(Date) } }

verify supports the same options as stubs:

  • times will require the demonstrated call happened exactly times times (by default, the call has to happen 1 or more times)
  • ignore_extra_args will allow the demonstration to forego specifying optional arguments while still being considered satisfied
  • ignore_block will similarly allow the demonstration to forego specifying a block, even if the actual call receives one

Note that if you want to verify a method wasn't called at all or called a specific number of times—especially if you don't care about the parameters, you may want to look at the Mocktail.calls() API.

Mocktail.matchers

You'll probably never need to call Mocktail.matchers directly, because it's the object that is passed to every demonstration block passed to stubs and verify. By default, a stubbing (e.g. stubs { email.send("text") }) is only considered satisfied if every argument passed to an actual call was passed an == check. Matchers allow us to relax or change that constraint for both regular arguments and keyword arguments so that our demonstrations can match more kinds of method invocations.

Matchers allow you to specify stubbings and verifications that look like this:

stubs { |m| email.send(m.is_a(String)) }.with { "I'm an email" }

Built-in matchers

These matchers come out of the box:

  • any - Will match any value (even nil) in the given argument position or keyword
  • is_a(type) - Will match when its type passes an is_a? check against the actual argument
  • includes(thing, [**more_things]) - Will match when all of its arguments are contained by the corresponding argument—be it a string, array, hash, or anything that responds to includes?
  • matches(pattern) - Will match when the provided string or pattern passes a match? test on the corresponding argument; usually used to match strings that contain a particular substring or pattern, but will work with any argument that responds to match?
  • not(thing) - Will only match when its argument does not equal (via !=) the actual argument
  • numeric - Will match when the actual argument is an instance of Integer, Float, or (if loaded) BigDecimal
  • that { |arg| … } - Takes a block that will receive the actual argument. If the block returns truthy, it's considered a match; otherwise, it's not a match.

Custom matchers

If you want to write your own matchers, check out the source for examples. Once you've implemented a class, just pass it to Mocktail.register_matcher in your test helper.

class MyAwesomeMatcher < Mocktail::Matchers::Base
  def self.matcher_name
    :awesome
  end

  def match?(actual)
    "#{@expected}✨" == actual
  end
end

Mocktail.register_matcher(MyAwesomeMatcher)

Then, a stubbing like this:

stubs { |m| user_repository.find(m.awesome(11)) }.with { :awesome_user }

user_repository.find("11")) # => nil
user_repository.find("11✨")) # => :awesome_user

Mocktail.captor

An argument captor is a special kind of matcher… really, it's a matcher factory. Suppose you have a verify call for which one of the expected arguments is really complicated. Since verify tends to be paired with fire-and-forget APIs that are being invoked for the side effect, this is a pretty common case. You want to be able to effectively snag that value and then run any number of specific assertions against it.

That's what Mocktail.captor is for. It's easiest to make sense of this by example. Given this BigApi class that's presumably being called by your subject at the end of a lot of other work building up a payload:

class BigApi
  def send(payload); end
end

You could capture the value of that payload as part of the verification of the call:

big_api = Mocktail.of(BigApi)

big_api.send({imagine: "that", this: "is", a: "huge", object: "!"})

payload_captor = Mocktail.captor
verify { big_api.send(payload_captor.capture) } # => nil!

The verify above will pass because a call did happen, but we haven't asserted anything beyond that yet. What really happened is that payload_captor.capture actually returned a matcher that will return true for any argument while also sneakily storing a copy of the argument value.

That's why we instantiated payload_captor with Mocktail.captor outside the demonstration block, so we can inspect its value after the verify call:

payload_captor = Mocktail.captor
verify { big_api.send(payload_captor.capture) } # => nil!

payload = payload_captor.value # {:imagine=>"that", :this=>"is", :a=>"huge", :object=>"!"}
assert_equal "huge", payload[:a]

Mocktail.replace

Mocktail was written to support isolated test-driven development, which usually results in a lot of boring classes and instance methods. But sometimes you need to mock singleton methods on classes or modules, and we support that too.

When you call Mocktail.replace(type), all of the singleton methods on the provided type are replaced with fake methods available for stubbing and verification. It's really that simple.

For example, if our Bartender class has a class method:

class Bartender
  def self.cliche_greeting
    ["It's 5 o'clock somewhere!", "Norm!"].sample
  end
end

We can replace the behavior of the overall class, and then stub how we'd like it to respond, in our test:

Mocktail.replace(Bartender)
stubs { Bartender.cliche_greeting }.with { "Norm!" }

[Obligatory warning: Mocktail does its best to ensure that other threads won't be affected when you replace the singleton methods on a type, but your mileage may very! Singleton methods are global and code that introspects or invokes a replaced method in a peculiar-enough way could lead to hard-to-track down bugs. (If this concerns you, then the fact that class methods are effectively global state may be a great reason not to rely too heavily on them!)]

Mocktail.explain

Test debugging is hard enough when there aren't fake objects flying every which way, so Mocktail tries to make it a little easier on you. In addition to returning useful messages throughout the API, the gem also includes an introspection method Mocktail.explain(thing), which returns a human-readable message and a reference object with useful attributes (that vary depending on the type of fake thing you pass in. Below are some things explain() can do.

Fake instances created by Mocktail

Any instances created by Mocktail.of or Mocktail.of_next can be passed to Mocktail.explain, and they will list out all the calls and stubbings made for each of their fake methods.

Suppose these interactions have occurred:

ice_tray = Mocktail.of(IceTray)

Mocktail.stubs { ice_tray.fill(:tap_water, 30) }.with { :some_ice }

ice_tray.fill(:tap_water, 50)

You can interrogate what's going on with the fake instance by passing it to explain:

explanation = Mocktail.explain(ice_tray)

explanation.reference.type      #=> IceTray
explanation.reference.double    #=> The ice_tray instance
explanation.reference.calls     #=> details on each invocation of each method
explanation.reference.stubbings #=> all stubbings configured for each method

Calling explanation.message will return:

This is a fake `IceTray' instance.

It has these mocked methods:
  - fill

`IceTray#fill' stubbings:

  fill(:tap_water, 30)

`IceTray#fill' calls:

  fill(:tap_water, 50)

Modules and classes with singleton methods replaced

If you've called Mocktail.replace() on a class or module, it can also be passed to Mocktail.explain() for a summary of all the stubbing configurations and calls made against its faked singleton methods for the currently running thread.

Imagine a Shop class with self.open! and self.close! singleton methods:

Mocktail.replace(Shop)

stubs { |m| Shop.open!(m.numeric) }.with { :a_bar }

Shop.open!(42)

Shop.close!(42)

explanation = Mocktail.explain(Shop)

explanation.reference.type      #=> Shop
explanation.reference.replaced_method_names #=> [:close!, :open!]
explanation.reference.calls     #=> details on each invocation of each method
explanation.reference.stubbings #=> all stubbings configured for each method

And explanation.message will return:

`Shop' is a class that has had its singleton methods faked.

It has these mocked methods:
  - close!
  - open!

`Shop.close!' has no stubbings.

`Shop.close!' calls:

  close!(42)

  close!(42)

`Shop.open!' stubbings:

  open!(numeric)

  open!(numeric)

`Shop.open!' calls:

  open!(42)

  open!(42)

Methods on faked instances and replaced types

In addition to passing the test double, you can also pass a reference to any fake method created by Mocktail to Mocktail.explain:

ice_tray = Mocktail.of(IceTray)

ice_tray.fill(:chilled, 50)

explanation = Mocktail.explain(ice_tray.method(:fill))

explanation.reference.receiver  #=> a reference to the `ice_tray` instance
explanation.reference.calls     #=> details on each invocation of the method
explanation.reference.stubbings #=> all stubbings configured for the method

The above may be handy in cases where you want to assert the number of calls of a method outside the Mocktail.verify API:

assert_equal 1, explanation.reference.calls.size

The explanation will also contain a message like this:

`IceTray#fill' has no stubbings.

`IceTray#fill' calls:

  fill(:chilled, 50)

Replaced singleton methods can also be passed to explain(), so something like Mocktail.explain(Shop.method(:open!)) from the earlier example would also work (with Shop being the receiver on the explanation's reference).

Undefined methods

There's no API for this one, but Mocktail also offers explanations for methods that don't exist yet. You'll see this error message whenever you try to call a method that doesn't exist on a test double. The message is designed to facilitate "paint-by-numbers" TDD, by including a sample definition of the method you had attempted to call that can be copy-pasted into a source listing:

class IceTray
end

ice_tray = Mocktail.of(IceTray)

ice_tray.fill(:water_type, 30)
# => No method `IceTray#fill' exists for call: (NoMethodError)
#
#      fill(:water_type, 30)
#
#    Need to define the method? Here's a sample definition:
#
#      def fill(water_type, arg)
#      end

From there, you can just copy-paste the provided method stub as a starting point for your new method:

class IceTray
  def fill(water_type, amount)
  end
end

Mocktail.explain_nils

Is a faked method returning nil and you don't understand why?

By default, methods faked by Mocktail will return nil when no stubbing is satisfied. A frequent frustration, therefore, is when the way stubs {}.with {} is configured does not satisfy a call the way you expected. To try to make debugging this a little bit easier, the gem provides a top-level Mocktail.explain_nils method that will return an array of summaries of every call to a faked method that failed to satisfy any stubbings.

For example, suppose you stub this fill method like so:

ice_tray = Mocktail.of(IceTray)

stubs { ice_tray.fill(:tap_water, 30) }.with { :normal_ice }

But then you find that your subject under test is just getting nil back and you don't understand why:

def prep
  ice = ice_tray.fill(:tap_water, 50)
  glass.add(ice) # => why is `ice` nil?!
end

Whenever you're confused by a nil, you can call Mocktail.explain_nils for an array containing UnsatisfyingCallExplanation objects (one for each call to a faked method that did not satisfy any configured stubbings).

The returned explanation objects will include both a reference object to explore as well a summary message:

def prep
  ice = ice_tray.fill(:tap_water, 50)
  puts Mocktail.explain_nils.first.message
  glass.add(ice)
end

Which will print:

This `nil' was returned by a mocked `IceTray#fill' method
because none of its configured stubbings were satisfied.

The actual call:

  fill(:tap_water, 50)

The call site:

  /path/to/your/code.rb:42:in `prep'

Stubbings configured prior to this call but not satisfied by it:

  fill(:tap_water, 30)

The reference object will have details of the call itself, an array of other_stubbings defined on the faked method, and a backtrace to determine which call site produced the unexpected nil value.

Mocktail.calls

When practicing test-driven development, you may want to ensure that a dependency wasn't called at all. To provide a terse way to express this, Mocktail offers a top-level calls(double, method_name = nil) method that returns an array of the calls to the mock (optionally filtered to a particular method name) in the order they were called.

Suppose you were writing a test of this method for example:

def import_users
  users_response = @gets_users.get
  if users_response.success?
    @upserts_users.upsert(users_response.data)
  end
end

A test case of the negative branch of that if statement (when success? is false) might simply want to assert that @upserts_users.upsert wasn't called at all, regardless of its parameters.

The easiest way to do this is to use Mocktail.calls() method, which is an alias of Mocktail.explain(double).reference.calls that can filter to a specific method name. In the case of a test of the above method, you could assert:

# Assert that the `upsert` method on the mock was never called
assert_equal 0, Mocktail.calls(@upserts_users, :upsert).size

# Assert that NO METHODS on the mock were called at all:
assert_equal 0, Mocktail.calls(@upserts_users).size

If you're interested in doing more complicated introspection in the nature of the calls, their ordering, and so forth, the calls method will return Mocktail::Call values with the args, kwargs, block, and information about the original class and method being mocked.

(While this behavior can technically be accomplished with verify(times: 0) { … }, it's verbose and error prone to do so. Because verify is careful to only assert exact argument matches, it can get pretty confusing to remember to tack on ignore_extra_args: true and to call the method with zero args to cover all cases.)

Mocktail.reset

This one's simple: you probably want to call Mocktail.reset after each test, but you definitely want to call it if you're using Mocktail.replace or Mocktail.of_next anywhere, since those will affect state that is shared across tests.

Calling reset in a teardown or after(:each) hook will also improve the usefulness of messages returned by Mocktail.explain and Mocktail.explain_nils.

References

Mocktail is designed following a somewhat academic understanding of what mocking is and how it should be used. Below are several references on this topic.

Blog Posts and Papers:

Books:

Talks:

Acknowledgements

Mocktail is created & maintained by the software agency Test Double. If you've ever come across our eponymously-named testdouble.js, you might find Mocktail's API to be quite similar. The term "test double" was originally coined by Gerard Meszaros in his book xUnit Test Patterns.

The name is inspired by the innovative Java mocking library Mockito. Mocktail also the spiritual successor to gimme, which offers a similar API but which fell victim to the limitations of Ruby 1.8.7 (and @searls's Ruby chops). Gimme was also one of the final projects we collaborated with Jim Weirich on, so this approach to isolated unit testing holds a special significance to us.

Code of Conduct

This project follows Test Double's code of conduct for all community interactions, including (but not limited to) one-on-one communications, public posts/comments, code reviews, pull requests, and GitHub issues. If violations occur, Test Double will take any action they deem appropriate for the infraction, up to and including blocking a user from the organization's repositories.

More Repositories

1

standard

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

testdouble.js

A minimal test double library for TDD with JavaScript
JavaScript
1,407
star
3

suture

🏥 A Ruby gem that helps you refactor your legacy code
Ruby
1,401
star
4

contributing-tests

1,104
star
5

scripty

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

test-smells

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

jasmine-rails

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

referral

🕵️‍♀️ Find, filter, and sort your Ruby code's definitions & references
Ruby
343
star
9

cypress-rails

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

good-migrations

Prevent Rails from auto-loading app/ code when running database migrations
Ruby
294
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