Giter Site home page Giter Site logo

interjectable's Introduction

Interjectable

Interjectable is a really simple Ruby library for dependency injection, designed to make unit testing easier.

Installation

It's a gem!

gem 'interjectable'

Usage

Interjectable has one module (Interjectable) and two main methods for defining dependencies, inject and inject_static. Use them like so!

class MyClass
  include Interjectable

  # defines helper methods on instances that memoize values per instance
  inject(:dependency) { SomeOtherClass.new }
  inject(:other_dependency) { AnotherClass.new }

  # defines helper methods on instances that memoize values statically,
  # shared across all instances
  inject_static(:shared_value) { ENV["SOME_VALUE"] }
end

It also includes introspection method injected_methods(include_super = true) (both instance and class-level) to track what dependency methods have been created.

MyClass.injected_methods
# => [:injected_methods, :shared_value, :shared_value=]
MyClass.new.injected_methods
# => [:injected_methods, :dependency, :dependency=, :other_dependency, :other_dependency=, :shared_value, :shared_value=]

This replaces a pattern we've used before, adding default dependencies in the constructor, or as memoized methods.

# OLD WAY, see above
class MyClass
  attr_accessor :dependency

  def initialize(dependency=SomeOtherClass.new)
    @dependency=dependency
  end

  def other_dependency
    @other_dependency ||= AnotherClass.new
  end
end

Dependency Injection + Unit Testing?

Ok but what the heck is dependency injection and what does it have to do with unit tests?

So in the real world, objects depend on other objects: object A uses object B to parse a file. This is normal. But what about when you want to test object A independently from object B? Object B might depend on objects C, D, E and so on, so the test would become needlessly complex.

For the sake of testing object A, we can stub out object B with something fake. But for normal usage of object A, we want to actually use object B (and all its dependencies). We can accommodate both these use cases with dependency injection!

Let's check it out: we can build a class A that normally references B, but in our test we can safely replace B, and don't even have to require or load B at all!

# a.rb
class A
  include Interjectable

  inject(:b) { B.new }
  inject_static(:c) { C.new }

  def read
    b.parse
  end

  def foo
    c.boom
  end
end

# a_spec.rb
require "a"
require "interjectable/rspec"

describe A do
  describe "#read" do
    before do
      # You can use the block form of #test_inject to inject a fake object that references methods on a.
      a.test_inject(:b) { FakeB.new(foo) }

      # You can use the test_inject RSpec helper if you just want to inject an object that doesn't
      # need to reference anything on a.
      test_inject(described_class, :c, instance_double(C, boom: "goat"))
    end

    it "parses from its b, and foos from its c" do
      expect(subject.read).to eq("result")
      expect(subject.foo).to eq("goat")
    end
  end

  # Both Interjectable.test_inject and the RSpec test_inject helper will setup
  # RSpec after hooks to cleanup any test_inject-ed dependencies after the
  # context they are defined in.
  it "doesn't pollute other tests" do
    expect(subject.read).to eq(B.new.parse)
    expect(subject.foo).to eq(C.new.boom)
  end
end

Great, why this library over any other?

Interjectable aims to provide clear defaults for easy debugging.

The other libraries we found used inject/provide setups. That setup is nice because files don't reference each other. However, this can become a headache to debug to actually figure out what is being used where. In our use cases, we use dependency injection for simplified testing, not for hands-free configuration.

interjectable's People

Contributors

zachmargolis avatar nerdrew avatar tim-shaker avatar mhickman avatar

Stargazers

 avatar Zach Cross avatar

Watchers

 avatar  avatar James Cloos avatar Isabel Fan avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.