Testing Ruby with RSpec: The Complete Guide
- 7.5 hours on-demand video
- 3 articles
- 2 downloadable resources
- Full lifetime access
- Access on mobile and TV
- Certificate of Completion
Get your team access to Udemy's top 3,000+ courses anytime, anywhere.Try Udemy for Business
- Utilize test-driven development principles to design and implement clean test specs in Ruby
Master the syntax and structure of RSpec, the most popular Ruby Gem for testing
Reduce dependencies in your test suite by mocking objects with class and instance doubles
- Explore the wide collection of RSpec matchers available to test your code
Welcome to the course! This lecture offers a quick introduction to the RSpec Gem as well as the benefits of testing. The various pieces of the library -- the core runner, expectations, and mocks -- are also introduced.
Unit tests target a specific "unit" or piece of an application, such as a single class or method. They create isolation between coupled components to ensure each piece stands by itself. In comparison, end-to-end tests test an application or a large feature as a whole. Integration tests fall somewhere in the middle. In this lesson, we talk extensively about these types of tests and introduce the testing pyramid.
Visual Studio Code is a popular open-source text editor that is supported by Microsoft. This lesson covers the installation of the software on a macOS machine as well as the setup of the recommended Ruby extension for syntax highlighting. Windows users are advised to follow the instructions on the VSCode website to setup the app on their computers. If you prefer another text editor, that is totally fine as well.
The rspec --init command creates a base skeleton for an RSpec project. It creates a spec_helper.rb file where high-level, global RSpec settings for the project can be declared. There are some recommended settings that can be enabled by removing the =begin and =end lines in the file.
Test-driven development (TDD) is a testing paradigm that argues that tests should be written first, before the code. This approach forces the developer to think critically about the implementation of the feature, one method at a time. This lesson describes the red-green-refactor pattern for practicing TDD. Red means a failing spec, green means a passing spec, and refactor means optimizing the code for clarity and efficiency.
The describe method on the top-level RSpec module creates an example group. An example group contains one or more examples. An example is the technical RSpec term for a test. In this lesson, we walk through the basic syntax and structure of setting up a sample example group (hey, that rhymes!)
The it method creates an example (i.e. a focused test). The it method accepts a string -- ideally, it should be one that makes the example read like a sentence (i.e. it "should shuffle the deck" or it "can communicate with database"). After the string argument, pass the method a block. The block will contain the assertions for that example.
The expect method accepts what will be evaluated --it can be an object, a class, a method, or a plain Ruby expression like 1 + 1. The method returns an object that includes a to method, which is invoked with a matcher. A matcher is a type of assertion; there are various matchers in RSpec for equality, inequality, identity, inclusion and more. In this lesson, we create a sample Card object and write our first expectation for it.
The rspec command in the Terminal can be followed with a path to a spec file's location. The command starts the RSpec test runner. The Terminal output includes a list of all examples that failed, including the line number that encountered an error or inconsistency. In this lesson, we read over the output and prepare to fix our failing specs.
It's time to make the Card class a reality! In this lesson, using our failing specs to guide us, we define a new Card class with a type attribute and a public reader method. It's important to not speed ahead and try to fix everything at once. Use each failure from the RSpec output to determine the next line of code to write. Fix one error, then run the rspec command again, and repeat.
In this lesson, we continue the practice of using our failing specs to drive the development of the code. We update the Card class to have two initialize arguments, two reader methods, and two instance variables. We also discuss some of the drawbacks of our current examples, particularly the duplication.
Multiple examples in a test suite often rely on the same piece of data or a common object. Duplicating the code to create or access that object in each example creates a lot of duplication. To DRY up the code, this lesson introduces before hooks and instance variables. A hook is a piece of code that runs automatically at a specific time during testing. By default, the before hook executes a block of code before each example. Within the block, we assign values to instance variables to preserve values once the block execution ends. These instance variables are then available to be used within any examples defined below.
The let method defines a memoized, lazy-loaded variable that is available to all examples in the current context. The method accepts a symbol for the name of the variable and a block for the value that variable should be assigned. Lazy-loaded means the variable will not be declared until it is used in a specific example. Memoized means multiple references to the variable in the same example will not require a reevaluation of the block passed to the let method.
describe method invocations can be nested inside other describe blocks. This is done to provide context about the specific circumstance or situation that an example is being run. context is an available alias for the describe method -- it can be used in the same fashion. In this lesson, we create nested describe blocks to test the even? predicate method in Ruby.
A hook allows the developer to run code during certain moments or events in the test suite's execution. The before and after run a block of code before or after a given criteria. When given a symbol of :example, the hook will run before / after each individual test. When given a symbol of :context, the hook will run once before / after all tests in the current block. In a real-life scenario, :context is used for high-level setup and teardown operations like connecting and disconnecting to a database (which is an expensive operation that you don't want to perform for each test).
Hooks gain an additional layer of complexity when describe / context blocks are nested within other describe / context blocks. The before(:context) hooks runs once before all tests in the current context (i.e. the current block). The before(:example) hook runs once before each test in the current context (which includes all nested blocks as well). If there is a before(:example) hook defined at multiple levels, each one will run in sequence, starting from the top-most block and proceeding downwards.
let variables can be set to different values in nested blocks. Ruby / RSpec will search for the name in the current scope, then proceed upwards if it is unable to find it. Instead of using multiple variable names and adding confusion, it's much more elegant to reuse the let variable and assign it new, relevant values in each testing context.
The subject helper method lazily instantiates an instance of the class under test. This offers the developer several advantages including (1) one less let variable and (2) shorthand syntax for expectations that we'll discuss later. By default, subject assumes the class will have no initialization arguments. Within the same example, subject will memoize / cache the object when it's used multiple times. Between different examples, subject will instantiate a fresh object. In this lesson, we utilize subject to test a Ruby hash.
The subject method can be invoked outside of any example. The last evaluation of the block passed to the method will serve as its return value. This allows the developer to instantiate a custom object from the class under test to serve as the base "subject" for testing. A subject declared in an outer scope (i.e. an outer block) will be available to all inner, nested blocks.
When a class is passed as an argument to the describe method, the class itself becomes available via the described_class method. It is advantageous to use described_class whenever the class is referenced because it makes the specs more adaptable to changes in business logic, such as the renaming of the class under test.
The use of either an implicit or explicit subject grants access to a special one-liner syntax for writing an example. First, the it method is passed a block without a string argument. Next, the method is_expected is invoked as a replacement for the expect syntax. Finally, a regular RSpec matcher (such as eq) is attached to the end. In this lesson, we demonstrate both syntactical options side by side.
The RSpec.shared_examples method is used to define commonly used examples that can be injected into multiple example groups. The method is passed a string identifier and a block where all the examples are declared. Later, the include_examples is invoked within the body of an example group and passed the string identifier. Multiple shared examples can be used; it is common to store these in a separate helper file that is imported by other spec files.
A complement to shared examples, the shared_context method allows for multiple example groups to rely on common boilerplate code. The developer can reduce duplication by declaring before blocks, instance variables, helper methods, and let variables in a shared context. The include_context method is then used to inject that context into multiple example groups.
This section is focused on the matchers available in RSpec. A matcher is a type of assertion, expectation or validation. RSpec includes a variety of matchers -- everything from equality to inclusion to identity to error raising. The not_to method is an alternative to the to method that checks for the inverse or negative of a given matcher. For example, not_to eq will check for inequality.
The eq matcher checks only for value equality. The stricter eql matcher checks for both value and data type. For example, eq would consider the integer value 3 and the floating point value 3.0 to be equal. eql would not consider the two equal because they are of different data types.
The equal matcher checks that two values are identical. To be identical, the values must both be the same object in the computer's memory. Identity is the strictest form of equality. In this lesson, we apply these matchers to arrays and introduce a real-life analogy of houses in a neighborhood.
RSpec includes support for numerical comparisons like greater than and less than or equal to. Use the be matcher followed by the proper Ruby operator. For example, expect(10).to be >= 5 will pass if 10 is found to be greater than or equal to 5.
A predicate method is one that returns a Boolean value -- either a true or a false. RSpec includes dynamic matchers for testing predicate methods. For any method, remove the question mark from the name and prefix it with be_. For example, the empty? method on an array can be tested with the predicate matcher be_empty.
Ruby has two falsy values: false and nil. All other objects are considered truthy, which means that they evaluate to true in a conditional context. In this lesson, we introduce the be_truthy and be_falsy matchers for asserting truthiness / falsiness as well as the be_nil matcher for explicit comparisons to nil.
One of the most powerful matchers in RSpec, the change matcher compares the value of something before and after an operation. Pass the expect method a block with some code that may mutate an object's state. Afterwards, invoke the change matcher with another block that describes what value is being tracked. Several comparison methods are available: the from / to combo as well as by.
The start_with and end_with matchers check for the presence of a smaller subset of elements at the beginning or end of a data structure. For a string, the matchers will check for a substring of characters. For an array, the matchers will check for the presence of elements in sequential order.
The have_attributes matcher confirms that an object has a set of attributes with corresponding values. The method accepts a hash where the key is the name of the attribute and the value is the corresponding attribute value. When testing a custom object, the attributes have to be publicly accessible via reader methods to be able to be checked.
The include matcher checks for the inclusion of an object within a larger object. It can be used to check for a substring within a string, a value within an array, a key within a hash, and even a key-value pair within a hash. This matcher is not concerned with order, only with presence.
The respond_to matcher verifies that an object can receive one or more given methods. The implementation and return values of the methods is not considered; this matcher checks only that the object has a method defined on its public interface. The additional with method confirms that the method is called with a specific number of arguments.
Verifying doubles are stricter alternatives to normal doubles. RSpec checks that the methods being stubbed are present on the underlying object. The instance_double method takes a class name or object as its argument. It verifies any method being stubbed would be present on an instance of that class.
- Intermediate knowledge of the Ruby programming language (classes, objects, data structures, etc)
- Modern version of Ruby (>2.4)
- Text editor (VSCode is recommended)
Welcome to Testing Ruby with RSpec: The Complete Guide!
This course offers a comprehensive overview of the RSpec testing library for the Ruby programming library. RSpec is the most popular Ruby Gem of all time, with over 300 million downloads to date.
If you're new to the topic, testing is the practice of "writing code that confirms that other code works as expected". Tests control for regressions, which are changes to the code that break the program.
The benefits of testing extend outside of the codebase. Adopting a test-driven approach will also make you a better developer. Tests force you to think critically about the program and its features: the classes, the objects, the methods and more.
Testing Ruby with RSpec begins with the essentials and proceeds to more complex topics including:
Test-Driven Development (TDD)
before and after Hooks
Mocks and Doubles
As a software engineer and consultant who's worked with Ruby for several years, I'm excited to introduce you to the awesome RSpec library, its elegant syntax, and all of its fun quirks.
Thanks for checking out the course!
- Intermediate Ruby developers interested in upgrading their skill set
- Programmers who want to explore the fundamentals of testing and TDD