Tealeaf Academy course three/week seven - mocks and stubs

This is a quick post to discuss the idea of mocks in Rails testing. I demonstrate this using the code I ended up with at the end of my last post:

class ProjectController < ApplicationController


  def create
    @project = Project.new(project_params)


(Click on the link to read my last post on the Beyond MVC)

Mocks in tests are used to simply record and verify expectations. In the example above I will use a mock to verify that CreditDeduction.new(current_user) does indeed receive the method deduct_credit but I don't care what actually happens after it receives the method.

For example, my controller test for the create action could look like this:

it "delegates to CreditDeduction to deduct_credit" do
  credit_deduction = double("credit_deduction")
  expect(credit_deduction).to receive(:deduct_credit)
  post: create, project: {name: "code project", description: "a test project"}

As you can see, I'm only checking to see to see if the method is actually received, nothing else. Stubs are slightly different and I'll use a previous code example to illustrate my point:

Here is a controller action:

def create
  @user = User.new(user_params)
  if @user.valid?
    attempt_card_payment = registration_payment_processor
    if attempt_card_payment.processed
      flash[:success] = "Thank you for registering, please sign in."
      redirect_to sign_in_path
    handle_create_error("Please fix the errors in this form.")


def registration_payment_processor
    amount: 999,
    email: @user.email_address,
    token: params[:stripeToken]

def handle_create_error(error)
  flash[:danger] = error
  render :new

And a snippet of a test for this action:

describe "POST create" do
  context "valid personal details" do

    context "valid card details" do

      let(:attempt_card_payment) { double(:attempt_card_payment) }
      before do
        expect(attempt_card_payment).to receive(:processed).and_return(true)
        allow(ExternalPaymentProcessor).to receive(:create_payment_process).and_return(attempt_card_payment) 

In this case I still don't want to actually run the create_payment_process on ExternalPaymentProcessor as was the case in the mock example but this time I do actually care about the state of ExternalPaymentProcessor because it affects the flow of my controller action. In this case I can test what happens if attempt_card_payment is true and can also write more tests to verify what happens when it is not.

It is a subtle difference between the two but hopefully this helps to clarify it a bit more. You can read more on this by reading this post by Martin Fowler.