Lesson 1
Introduction to Dependencies in Testing with Ruby
Introduction

Welcome to the first lesson of our course on managing test doubles in Ruby. Dependencies are components or services that your software relies on to function, like databases, logging systems, or external APIs. However, when testing, these dependencies can introduce variability, making it hard to test your code's logic reliably. Test doubles allow you to replace these real dependencies with simpler objects that mimic their behavior. This ensures tests focus solely on your code's logic without interference from external systems. For instance, by isolating an email service’s logging component using a test double, you can test email-related functionality without generating actual log entries.

What You’ll Learn

In this lesson, we will explore the concept of dependencies in software testing and introduce you to the use of test doubles, starting with dummies, the simplest form of test doubles. During this course, we'll discuss five kinds of test doubles:

  • Dummies: These are simple placeholders used to fulfill parameter requirements. They have no logic or behavior beyond satisfying an interface or method signature.
  • Stubs: These provide predefined responses to specific calls during testing, allowing you to control the behavior of certain dependencies without implementing full functionality.
  • Spies: These track information about interactions with dependencies, such as method calls and arguments, enabling you to verify behaviors indirectly.
  • Mocks: These are more sophisticated test doubles that are both stubs and spies. They allow you to set expectations and verify that certain interactions occur during testing.
  • Fakes: These are simpler implementations of complex behavior that are useful for testing, typically with some working logic, often used to simulate a real system or component.

During this lesson, you'll learn how dummies provide a straightforward way to address dependencies by serving as simple placeholders without any logic. By the end, you'll have a foundational understanding of how to utilize dummies in your workflow, paving the way for more complex test doubles in future lessons.

Example of Using Dummies

Let's see dummies in action by setting up tests for an EmailService application. Here's how you can create an email_service_spec.rb file with dummies using the RSpec framework:

Ruby
1require 'rspec' 2require_relative 'email_service' 3 4# DummyLogger: A stand-in for a real logger, no actual logging happens. 5class DummyLogger 6 def log(_message) 7 # No operation performed 8 end 9end 10 11# DummyEmailSender: A placeholder for a real email sender, doesn't send emails. 12class DummyEmailSender 13 def send(_to, _subject, _body) 14 # No operation performed 15 end 16end 17 18RSpec.describe EmailService do 19 # Sets up the email service with dummy dependencies 20 let(:email_service) { EmailService.new(DummyLogger.new, DummyEmailSender.new) } 21 22 it 'accepts valid email parameters' do 23 # Tests basic functionality with valid inputs 24 result = email_service.send_email('test@example.com', 'Hello', 'This is a test') 25 expect(result).to be_truthy 26 end 27 28 # Additional tests for other cases... 29end

In this example:

  • DummyLogger and DummyEmailSender act as stand-ins for real implementations. They don't have any behavior; they just satisfy the interface requirements of EmailService.
  • By using dummies, you reduce complexity and ensure that the tests are focusing solely on the logic within EmailService.

This method provides an introduction to isolating dependencies with minimal effort, setting the stage for learning about more advanced test doubles like stubs, mocks, and fakes.

Implementing the `EmailService`

To fully understand how dummies integrate into your testing workflow, let's look at the implementation of the EmailService that the tests are targeting. Here's how you can set up the email_service.rb file:

email_service.rb

Ruby
1class EmailService 2 def initialize(logger, email_sender) 3 @logger = logger 4 @email_sender = email_sender 5 end 6 7 def send_email(to, subject, body) 8 return false if to.nil? || !to.include?('@') 9 return false if subject.nil? 10 return false if body.nil? || body.length > 1000 11 12 @logger.log("Sending email to #{to}") 13 @email_sender.send(to, subject, body) 14 true 15 end 16end

This implementation outlines a basic EmailService class, using a logger and an email sender as dependencies. The method send_email performs basic validation checks on the email parameters and, if they're valid, logs and sends the email using the dependencies provided. By using dummies to stand in for Logger and EmailSender, you can isolate this email-sending logic from the complexities of actual logging and email-sending functionalities, ensuring a focused test environment.

Summary and Preparing for Practice

In this lesson, you were introduced to the concept of dependencies in testing and how test doubles, specifically dummies, can help isolate these dependencies. Dummies serve as simple placeholders without behavior, allowing you to concentrate on testing the core logic of your application without external complexities. By using dummies, you can simplify test setups and focus more effectively on the behavior of the code under test. This foundational understanding sets the stage for exploring more advanced test doubles in future lessons.

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