Isolate like a boss
with Bogus

psyho/bogus

The system

Awesome Blog 2.0 PRO

The devs

1
2
3
4
describe PostAdder do
  it "saves the blog posts" # ...
  it "notifies the administrators" # ...
end
1
2
3
4
5
6
7
8
9
10
11
12
13
describe PostAdder do
  before do
    PostAdder.add(title: "Hello")
  end

  it "saves the blog posts" do
    Post.exists?(title: "Hello").should be_true
  end

  it "notifies the administrators" do
    AdminMailer.deliveries.size.should == 1
  end
end
1
2
3
4
5
6
class PostAdder
  def self.add(details)
    post = PostRepository.add(details)
    AdminMailer.post_added(post).deliver
  end
end

Eventually, the tests become slow...


$ rspec spec
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
...................

Finished in 15 minutes 32 seconds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
describe PostAdder do
  let(:post) { build(:post) }

  before do
    stub_class("PostRepository")
    stub_class("AdminNotifier")

    stub(PostRepository).add { post }
    stub(AdminNotifier).post_added(post)
  end

  it "saves the blog posts" do
    mock(PostRepository).add(title: "Hello") { post }

    PostAdder.add(title: "Hello")
  end

  it "notifies the administrators" # ...
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
describe PostAdder do
  let(:post) { build(:post) }

  before do
    stub_class("PostRepository")
    stub_class("AdminNotifier")

    stub(PostRepository).add { post }
    stub(AdminNotifier).post_added(post)
  end

  it "saves the blog posts" # ...

  it "notifies the administrators" do
    mock(AdminNotifier).post_added(post)

    PostAdder.add(title: "Hello")
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
class PostAdder
  def self.add(details)
    post = PostRepository.add(details)
    AdminNotifier.post_added(post)
  end
end

class AdminNotifier
  def self.post_added(post)
    AdminMailer.post_added(post).deliver
  end
end

Tests run fast again


$ rspec spec
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
...................

Finished in 5 seconds
1
2
3
4
5
6
# before
class AdminNotifier
  def self.post_added(post)
    AdminMailer.post_added(post).deliver
  end
end
1
2
3
4
5
6
# after
class AdminNotifier
  def self.post_changed(post)
    AdminMailer.post_changed(post).deliver
  end
end

$ rspec spec
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
...................

Finished in 5 seconds

Two hours later on CI

Fire!
1
2
3
4
# Gemfile
group :test do
  gem 'bogus'
end
1
2
3
# spec/spec_helper.rb

require 'bogus/rspec'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
describe PostAdder do
  before do
    fake_class(PostRepository)
    fake_class(AdminNotifier)

    stub(PostRepository).add(any_args) { post }

    # NameError!
    stub(AdminNotifier).post_added(post)
  end

  it "saves the blog posts" # ...
  it "notifies the administrators" # ...
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
describe PostAdder do
  let(:post) { build(:post) }
  let(:details) { {title: "Hello"} }

  before do
    fake_class(PostRepository, add: post)
    fake_class(AdminNotifier)

    PostAdder.add(details)
  end

  it "saves the blog posts" do
    PostRepository.should have_received.add(details)
  end

  it "notifies the administrators" do
    AdminNotifier.should have_received.post_changed(post)
  end
end
1
2
3
4
5
6
# before
class AdminNotifier
  def self.post_changed(post)
    AdminMailer.post_changed(post).deliver
  end
end
1
2
3
4
5
6
7
# after
class AdminNotifier
  def self.post_changed(post, author)
    return if author.admin?
    AdminMailer.post_changed(post).deliver
  end
end

$ rspec spec
......................................
......................................
......................................
......................................
......................................
......................................
......................................
...................F

Finished in 5 seconds
1 Failure
1
2
3
4
5
class SocialMediaNotifier
  def self.post_added(post)
    # takes about 30s to run
  end
end
1
2
3
4
5
6
7
8
class PostAdder
  def self.add(details)
    post = PostRepository.add(details)
    AdminNotifier.post_changed(post, post.author)

    SocialMediaNotifier.post_added(post)
  end
end

$ rspec spec
......................................
......................................
......................................
......................................
......................................
......................................
......................................
......................................
...................

Finished in 1 minute 5 seconds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
describe PostAdder do
  let(:post_repository) {
    fake(:post_repository, as: :class, add: post)
  }
  fake(:admin_notifier, as: :class)
  fake(:social_media_notifier, as: :class)

  let(:post_adder) {
    PostAdder.new(post_repository,
                  admin_notifier,
                  social_media_notifier) }

  before { post_adder.add(details) }

  it "saves the blog posts" # ...
  it "notifies the administrators" # ...
  it "notifies social media sites" # ...
end
1
2
3
4
5
6
7
describe PostAdder do
  let(:post_repository) {
    fake(:post_repository, add: post)
  }
  fake(:admin_notifier)
  fake(:social_media_notifier)
end
1
2
3
4
5
Bogus.fakes do
  fake(:post_repository, as: :class)
  fake(:admin_notifier, as: :class)
  fake(:social_media_notifier, as: :class)
end
1
2
3
describe PostAdder do
  let(:post_adder) { isolate(PostAdder) }
end
1
2
3
4
class PostAdder
  takes :post_repository, :admin_notifier,
        :social_media_notifier
end
There's more

Other features

Check out the docs

Bogus

Me

@apohorecki
github.com/psyho

[sckrk]
you shall ask
questions now

Thanks!