Test Doubles - Simple Stubs

This topic is for the [Simple Stubs] exercise in the [Test Doubles] trail. Post any questions, corrections, or pointers you have to share with other Upcase subscribers.
[Simple Stubs]: https://exercises.upcase.com/exercises/simple-stubs
[Test Doubles]: Test Doubles | RSpec Stub Online Tutorial by thoughtbot

There’s something about test doubles that makes me feel like a complete idiot. I hardly even know how best to formulate what I’m not getting. But here goes:

There’s a bunch of duplicated data between our two tests: the creation of two posts from today and one from yesterday. I don’t understand how creating a dummy object allows me to eliminate this redundancy. The array that my test is expecting ([first_today, last_today]) has to come from somewhere, and I don’t understand from the examples preceding the exercise, or from the video, how I’m supposed to use my double and its stubbed method to produce that array.

@MatthewMDavis using doubles is going to be a change from how you’ve approached testing in the past, so there’s no need to worry about it being unfamiliar.

Take a look at the instructions around stubbing the class method on Post. You’re going to want to stub out that behavior to return your double rather than allow Post to look for real records in the database.

Two of the advantages you’ll get from testing with doubles and stubs will be that you’re going to be able to avoid touching the database (which will speed up your test execution considerably) and that your controller tests will be less brittle if you have subtle changes in the results that a change in the model (maybe .today in the future doesn’t end at midnight, but ends at sundown, etc.)

Hello all.

I am a little bit confused as to what extent we should use mocks and stubs in our tests. Considering that tests should verify if your methods are working properly; if I do something like that:

describe Dashboard do
  describe "#posts" do
    it "returns posts created today" do
      posts = double("posts_created_today")

      allow(Dashboard).to receive(:new).with(posts).and_return(posts)
      allow(Dashboard).to receive(:posts).and_return(posts)

      expect(Dashboard.posts).to eq(posts)
    end
  end
end

This test is going to be green even though I change the #posts method completely.

I’m guessing that of course, we shouldn’t mock or stub everything in tests. What would be a different approach for this particular test?

Thank you!

@aclapinpepin In general, you shouldn’t stub methods of the object you’re testing, only on the objects it collaborates with.

The article Don’t Stub the System Under Test provides a good explanation of this.

In this case, you could stub a method on the Post class which returns today’s posts.

Thank you @andyw8! This was a very enlightening article.

It seems like something is missing in the example examplified here: https://exercises.upcase.com/exercises/simple-stubs

For example, if I was to change the logic to only show the five most recent post, how would I change “allow(Post).to receive(:latest_published).and_return(posts)”?

Also the first example also doesn’t seem to be the best as it’s showing every post that was created. Hence, even if you didn’t yet include the logic to limit it to 10, it would still pass.

@geoffharcourt @Upcase any answer on this? I also don’t understand how the refactor in the example is finding the 10 latest published posts - it seems like it is finding just the last published post?

Hi @sstgithub,

In this example, we’re using the controller unit test to verify that the correct model scope (.latest_published) gets called. The double that gets returned represents the collection of posts that Post.latest_published ordinarily returns. I would then verify the query from the scope a model spec for post. This way, if your scope incorporates some new behaviors that require testing against edge cases, you only need to update the model spec, and not both the controller and the model specs.

I try to test the logic at the point where it’s defined. You might later write an integration spec that avoided using any stubs or mocks that exercised the whole stack to confirm that the contract between Post and PostController was valid (the methods called in PostController exist in Post and return what’s expected). Integration tests are a valuable part of your overall testing strategy, but they tend to be slower because they are sending requests through the webserver and interact with the database.

When we are starting the application and writing the spec for the controller we won’t have the Post class and how are we expecting Post.all to work?

1 Like

Hi all,
I have a question since I felt confused about the exercise.

The question is Why the original code didn’t test the method itself?

In this exercise, I think it asks us to test the instance method #posts in Dashboard Model. Buy why the original code didn’t really test the method it self.
The original code is

expect(result.map(&:title)).to match_array(%w(first_today last_today))

But I think it should be like

expect(dashboard.posts) ...

Or did I miss or misunderstand something?

Thanks