Lesson 5
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:

Ruby
1class User 2 # Define user properties with attr_accessor for easy read/write access 3 attr_accessor :id, :email, :name, :created_at 4 5 # Initialize a user with provided attributes 6 def initialize(id:, email:, name:, created_at:) 7 @id = id 8 @name = name 9 @created_at = created_at 10 @email = email 11 end 12end 13 14class InMemoryUserRepository 15 def initialize 16 # Store users in a hash with user id as the key 17 @users = {} 18 # Start with an initial user ID 19 @current_id = 1 20 end 21 22 # Generate a new user ID and increment the current_id 23 def generate_id 24 id = @current_id.to_s 25 @current_id += 1 26 id 27 end 28 29 # Create a new user and store it in the repository 30 def create(user_data) 31 user = User.new( 32 id: generate_id, 33 email: user_data[:email], 34 name: user_data[:name], 35 created_at: Time.now 36 ) 37 @users[user.id] = user 38 user 39 end 40 41 # Find a user by their ID 42 def find_by_id(id) 43 @users[id] 44 end 45 46 # Find a user by their email 47 def find_by_email(email) 48 @users.values.find { |user| user.email == email } 49 end 50 51 # Update a user with new data 52 def update(id, data) 53 existing = @users[id] 54 return unless existing 55 56 updated = User.new( 57 id: existing.id, 58 email: data.fetch(:email, existing.email), 59 name: data.fetch(:name, existing.name), 60 created_at: existing.created_at 61 ) 62 @users[id] = updated 63 updated 64 end 65 66 # Delete a user by their ID and return a boolean success indicator 67 def delete(id) 68 !!@users.delete(id) 69 end 70 71 # Retrieve all users 72 def find_all 73 @users.values 74 end 75 76 # Clear all users and reset the current_id for testing isolation 77 def clear 78 @users.clear 79 @current_id = 1 80 end 81end

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.

Building Tests Using the Fake Repository

Next, we will use the fake repository to test a UserService.

  1. Red: Write Failing Tests

In user_service_spec.rb:

Ruby
1require 'rspec' 2 3class UserService 4 # Initialize the service with a repository dependency 5 def initialize(repository) 6 @repository = repository 7 end 8 9 # Register a new user, using the repository to create the user 10 def register_user(email, name) 11 @repository.create(email: email, name: name) 12 end 13end 14 15RSpec.describe UserService do 16 # Setup a user repository and service before each test 17 before(:each) do 18 @user_repository = InMemoryUserRepository.new 19 @service = UserService.new(@user_repository) 20 end 21 22 # Clear the repository after each test to maintain test isolation 23 after(:each) do 24 @user_repository.clear 25 end 26 27 # Test user registration functionality 28 it 'registers a user' do 29 # Act: Register a new user 30 user = @service.register_user('test@example.com', 'Test User') 31 32 # Assert: Validate user details and repository behavior 33 expect(user.email).to eq('test@example.com') 34 expect(user.name).to eq('Test User') 35 expect(user.id).not_to be_nil 36 expect(user.created_at).to be_a(Time) 37 expect(@user_repository.find_all.size).to eq(1) 38 end 39end

This test example assumes that the UserService logic is not implemented yet, resulting in a failing test.

  1. Green: Implement Minimal Code

Example implementation in user_service.rb:

Ruby
1class UserService 2 # Initialize the service with a repository dependency 3 def initialize(repository) 4 @repository = repository 5 end 6 7 # Register a new user, using the repository to create the user 8 def register_user(email, name) 9 @repository.create(email: email, name: name) 10 end 11end

This shows a minimal implementation that satisfies the test criteria, demonstrating how the test can be made to pass.

Review, Summary, and Preparation for Practice Exercises

In this lesson, we explored the implementation and use of fakes in TDD, specifically via an in-memory repository for user management. Remember the steps of TDD:

  • Red: Write a test that fails first, setting clear goals for implementation.
  • Green: Implement just enough code to make your test pass.
  • Refactor: Improve code quality without altering functionality.

Leverage the practice exercises to reinforce these concepts with hands-on examples. Congratulations on navigating the complexities of testing with fakes; your commitment is paving the way for building efficient, scalable applications. This is the final lesson of the course, so kudos for reaching this milestone! Keep exploring and applying TDD principles in your projects.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.