Learning Goals
- 1.
Learn about RSpec before and after hooks.
- 2.
Learn about the subject reference.
- 3.
Learn about named subjects.
- 4.
Learn about how to extend RSpec with helper methods in modules.
- 5.
Learn about RSpec metadata.
- 6.
Learn about filtering.
Hooks
You have already learned about the before hook that RSpec offers and allows you to prepare the context, that is, the setup of your test, of your example. However, RSpec offers some extra hooks.
before(:suite)
At the Beginning of spec/sandwich_spec.rb
Full Version of spec/sandwich_spec.rb
The before :suite, since it is not related to any specific example or example group, can only be registered from within an RSpec configuration block. This is what the preceding lines 3–7 do. You start an RSpec.configuration block, and you call config.before :suite do ... end in order to register code that will be executed at the start of the spec runner.
You can see the message This is executed only once at the beginning of the specifications run that is being printed at the beginning (after the Randomized with seed 1770).
The before :suite hook is usually used to set the correct test execution context for all the tests in your suite, for example, to load your test database with some seed data.
Moreover, because of the fact that it is executed before the instantiation of the examples, you cannot instantiate variables and expect to have them available inside the blocks of code of your examples.
Use the Instance Variable @foo
spec/spec_helper.rb File with the before(:suite) Hook
After you do the preceding change, don’t forget to bring spec/spec_helper.rb back to its original state: without the RSpec.configure block and without the expectation for @foo to be 5 inside the is delicious example.
Then, make sure that if you run the whole suite, everything is still green.
before(:all)
This hook attaches code that is executed once for all the examples on a given context/describe. So this should appear inside the do...end block of a describe or context.
spec/coffee_spec.rb File with the before :all Hook
- 1.
You have added two more expectations that have to do with the duration to prepare a cup of coffee.
- 2.
You have added a before(:all) hook on the context with milk. This code will be executed only once for the two examples in that context.
Introduce Method prepare_duration
before(:all) is also available as before(:context).
before(:example) or Simply before
You have already seen this hook. It is used to attach code that will be executed before every example in the context/describe it is declared in.
This is what happens on line 20 before { coffee.add_milk } inside the file spec/coffee_spec.rb.
Note that before(:example) is also available as before(:each).
after Hooks
- 1.
after :example, which is executed after every example. Also available as after :each or simply after
- 2.
after :all, which is executed after every context. Also available as after :context
- 3.
after :suite, which is executed at the end of the execution of the whole suite of examples
Multiple Hooks
Note that you can attach multiple hooks. They will be executed in the order they are declared.
subject
Let’s now talk about another technique and best practice that you will frequently encounter. It is about the subject under test.
In that case, you can use the method subject inside your example code. Then the subject is going to be an instance of that class, instantiated with the default initializer.
Use the Coffee class
Now the Coffee string is printed as the outermost describe description.
Use subject
You have removed the let(:coffee)... and all the coffee references. You are now using the subject instead.
If you run your specs for the Coffee, you will see that they will succeed.
So subject saves a little bit of typing, but the problem is that it hides the semantic meaning of the subject under test and the firing phase code is not really easy to understand quickly what it is about. In other words, the coffee.prepare_duration is much more descriptive and friendly to the reader rather than the subject.prepare_duration.
Using let and subject
This version goes back to be using coffee instead of subject inside the example code. But the coffee is defined in terms of the subject.
spec/sandwich_spec.rb Using subject
- 1.
On line 3, I use RSpec.describe Sandwich do.
- 2.
On lines 4–6, I define sandwich as a let of the subject.
On line spec/sandwich_spec.rb:5, you are calling subject which internally calls the Sandwich initializer without any arguments. That’s why you are getting ArgumentError and (0 for 2), that is, the initializer has been called with no arguments when two were expected.
Redefine subject
You create a named subject, you give it the name sandwich, and then you can use it in your example code.
One might ask, “Why would you want to do that and not just use let?” The answer is that I prefer to use subject to indicate the subject under test. I use let for other helper methods that I might need for my tests.
Note that subject is being memoized and evaluated only once within the execution of an example. So, even if your example calls the named subject multiple times, only the first time is being evaluated, as the let does.
Finally, both subject and let have the alternative subject! and let!, respectively. The subject! and let! evaluate their blocks before the example execution (whereas the subject and let are lazily evaluated at the first occurrence within the example code).
Extending RSpec with Helper Methods in Modules
You have learned how to create helper methods inside an example group. It has been presented in the previous chapter. Those methods were available inside the context in which they were defined, but not outside of it.
RSpec offers you the ability to define helper methods that can be used by any context, since they are attached at the RSpec configuration level.
How do you do that?
Example Module with a Helper Method
This module defines a helper method that takes a string value and returns it with all characters upcased. Also, it adds an extra space after each character (except the last one).
In order for you to be able to use this helper method, you need to attach the module to the RSpec configuration as follows:
And in order for the UpcasedAndLineSpaced constant to be found, you will have to require the module file before the RSpec.configure block.
spec/spec_helper.rb
Using the Helper Method from the Module
You have added a new specification. See lines 12–14. It is there where you call the upcased_and_line_spaced helper method.
This is a very common practice with RSpec. You will encounter it in many projects. Note that when you do config.include <module>, the methods become available inside the example code. However, when you do config.extend <module>, the methods become available inside the do..end blocks of the describes and contexts.
Metadata
RSpec builds a set of metadata around your example groups and examples. Also, it allows you to build your custom metadata.
described_class
The call to described_class returns the class that is described when the outermost describe is given a class (and not a string description). You usually use it in order to avoid repeating the class name, hence being easier to change it, if you need to.
Using described_class
The only change is on line 5. Instead of Sandwich.new, you are using described_class.new. Again, this allows you to change the class at the top call to described without having to change its other occurrences inside the spec file.
Custom Metadata
RSpec allows you to attach metadata to example groups or examples. The metadata are given in the form of a Hash as the last argument to describe, context, or it, before the block definition.
Example of Custom Metadata
You have attached the metadata test: :unit.
Access Metadata
Note that the metadata are being inherited from the example group to all the enclosed examples and other example groups (describes or contexts). And each contained example group or example can add more metadata or override the value of metadata inherited.
Override Metadata Values
As you can see in the preceding code, the lets me add toppings has type of test integration, whereas the is displayed as D E L I C I O U S has type of test unit.
In this example, you are attaching the metadata key :wip. This is attached with the value true, even if you have not explicitly set that.
Filtering
Metadata are also useful with regard to what examples you might want to run when invoking rspec. In other words, you can limit the selected examples according to their metadata values.
As you can see in the preceding code, you have invoked rspec with the --tag test:integration. This filtered the examples to run only the ones that have the metadata tag test with value integration.
Tasks and Quizzes
This chapter is coming both with a quiz and a task.
Quiz
The quiz for this chapter can be found here: www.techcareerbooster.com/quizzes/useful-rspec-tools.
Task Details
- (1)
The purpose of this class is to take as input a multiline string and return back each line of the string being suffixed with its corresponding line number. For example, if you have the string
This is a multi-linestring. This is second lineAnd this is third linethe class should return back this:1This is a multi-line2string. This is second line3And this is third line - (2)Besides this main functionality, the class should allow for some kind of configuration. For example, you want to tell that each number should be suffixed with the string ". ". If you say that, then the returned string should be something like this:1. This is a multi-line2. string. This is second line3. And this is third line
- (3)Or you may say that the line numbering should start from 8, not 1. In that case, the output should be something like this:8This is a multi-line9string. This is second line10And this is third line
- (4)You may also say that you want each number to be padded with 0s or other character sequence. For example, assuming that you want the numbers to be padded with blanks, the input isThis is a multi-linestring. This is a second lineAnd this is the third lineAnd this is the fourth lineAnd this is the fifth lineAnd this is the sixth lineAnd this is the seventh lineAnd this is the eighth lineAnd this is the ninth lineAnd this is the tenth lineAnd this is the eleventh lineAnd then the output will be1This is a multi-line2string. This is a second line3And this is the third line4And this is the fourth line5And this is the fifth line6And this is the sixth line7And this is the seventh line8And this is the eighth line9And this is the ninth line10And this is the tenth line11And this is the eleventh line
- (5)However, when the number of lines does not justify padding, then padding will not be used even if specified. Hence, if you specify that you want padding with 0s but the input is less than ten lines long, then no padding will take place. In other words, the inputline 1line 2is output as1line 12line 2
- (6)
You may also ask the class to prefix each line with a prefix string. Assume that you have the following input and the prefix you want to attach to each line is '#':
line 1line 2
- 1.
Starting Number: Default value is 1.
- 2.
Pad Numbers With: A string to pad numbers with. Numbers are right justified, and the padding string is repeated to the left.
- 3.
Suffix Number With: A string to suffix each number with.
- 4.
Prefix Line With: A string to prefix each line with.
Please note that you don’t have to stick to the RSpec specifications that would give the same output as the one presented at the top of this task. You can come up with your own implementation for the RSpec specifications. What I want you to do is to implement the class and make sure that it is well test covered.
Note that this task is inspired by this online text mechanics page: http://textmechanic.com/text-tools/numeration-tools/number-each-line/.
Key Takeaways
How to use RSpec hooks
How to use subject and named subjects
How to extend RSpec with helper methods
How to use RSpec metadata
In the following chapter, you will be introduced to another very popular tool that lives in the BDD (Behavior-Driven Development): Cucumber. Cucumber allows you to write your specifications in plain English.