Introduction to Fakes in TDD with Ruby and RSpec

Introduction to Fakes in TDD

Welcome to our lesson on using Fakes as test doubles in Test-Driven Development (TDD) with Ruby and RSpec. In this lesson, you'll explore how fakes can streamline your testing by simulating real-world components. Our journey so far has exposed you to various test doubles like dummies, stubs, spies, and mocks. Now, we'll dive into fakes, which enable you to create realistic implementations that mirror complex dependencies, making your tests more robust and reliable. As always, we'll practice the TDD cycle: Red, Green, Refactor, as we see how fakes fit into our testing strategy.

Code Example and Walkthrough: Implementing an In-memory Fake Repository

Let's see how to implement a simple fake: an InMemoryUserRepository. This serves as a stand-in for a real database repository, providing controlled behavior for our tests.

Create in_memory_user_repository.rb:

class User
  # Define user properties with attr_accessor for easy read/write access
  attr_accessor :id, :email, :name, :created_at

  # Initialize a user with provided attributes
  def initialize(id:, email:, name:, created_at:)
    @id = id
    @name = name
    @created_at = created_at
    @email = email
  end
end

class InMemoryUserRepository
  def initialize
    # Store users in a hash with user id as the key
    @users = {}
    # Start with an initial user ID
    @current_id = 1
  end

  # Generate a new user ID and increment the current_id
  def generate_id
    id = @current_id.to_s
    @current_id += 1
    id
  end

  # Create a new user and store it in the repository
  def create(user_data)
    user = User.new(
      id: generate_id,
      email: user_data[:email],
      name: user_data[:name],
      created_at: Time.now
    )
    @users[user.id] = user
    user
  end

  # Find a user by their ID
  def find_by_id(id)
    @users[id]
  end

  # Find a user by their email
  def find_by_email(email)
    @users.values.find { |user| user.email == email }
  end

  # Update a user with new data
  def update(id, data)
    existing = @users[id]
    return unless existing

    updated = User.new(
      id: existing.id,
      email: data.fetch(:email, existing.email),
      name: data.fetch(:name, existing.name),
      created_at: existing.created_at
    )
    @users[id] = updated
    updated
  end

  # Delete a user by their ID and return a boolean success indicator
  def delete(id)
    !!@users.delete(id)
  end

  # Retrieve all users
  def find_all
    @users.values
  end

  # Clear all users and reset the current_id for testing isolation
  def clear
    @users.clear
    @current_id = 1
  end
end

Explanation:

  • We create in-memory storage for users using a hash (@users).
  • Each function simulates typical database operations like create, find_by_id, and find_all.
  • The clear method ensures data isolation between tests, a crucial feature for repeatable outcomes.

By using a controlled data store, we make sure our tests focus on business logic and are not dependent on an external database. Fakes are often quite complicated (compared to mocks or stubs) to build because they mimic the behavior of the real thing. They can be used to verify the state after your code acts on the fake, which can be really useful when you are trying to mimic the environment as best as possible without introducing the uncertainty or delay that the real implementation would introduce.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal