We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

RSpec: It's Not Actually Magic

00:00

Formal Metadata

Title
RSpec: It's Not Actually Magic
Title of Series
Part Number
58
Number of Parts
94
Author
License
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
RSpec is often described with the word “magic” by both its users and its detractors. Understanding how RSpec matchers, doubles, and specifications work will help you as an RSpec user. You will be able to take advantage of RSpec’s flexibility to make your tests clearer and more expressive. You’ll also get some exposure to new RSpec features, like compound matchers. Walking through a typical RSpec example, we’ll show the RSpec internals as RSpec evaluates matchers and uses doubles. You’ll leave with a better understanding of how to harness RSpec in your own tests.
AliasingValidity (statistics)Forcing (mathematics)CASE <Informatik>Different (Kate Ryan album)Matching (graph theory)Client (computing)Expected valueSign (mathematics)Block (periodic table)Revision controlPredicate (grammar)Range (statistics)MappingBitGroup actionRight angleObject (grammar)Software testingMultiplication signMaxima and minimaProxy serverProjective planeQuicksortEstimatorTerm (mathematics)Core dumpSocial classCodeOptical disc driveGoodness of fitParameter (computer programming)INTEGRALWrapper (data mining)Software developerSymbol tableComplex (psychology)Task (computing)ExpressionProduct (business)Endliche ModelltheorieElectronic mailing listTwitterMereologySimilarity (geometry)Slide ruleCuboidFerry CorstenSpacetimeFunctional (mathematics)Representation (politics)Key (cryptography)SurfaceUtility softwareLevel (video gaming)Wave packetInformationPoint (geometry)Line (geometry)Inheritance (object-oriented programming)Covering spaceSet (mathematics)Doubling the cubeDampingForm (programming)Normal (geometry)Shape (magazine)MetadataDescriptive statisticsPosition operatorFreezingContext awarenessRule of inferenceEntire functionVideo gameException handlingGame controllerOcean currentKeyboard shortcutInstance (computer science)Message passingTable (information)NumberMixture modelString (computer science)Sensitivity analysisGreen's functionProblemorientierte ProgrammierspracheSource codeDependent and independent variablesNatural languageProcess (computing)Stack (abstract data type)1 (number)Default (computer science)Asynchronous Transfer ModeFile formatExpert systemPerformance appraisalResultantData structureDigital watermarkingAuditory maskingRegulärer Ausdruck <Textverarbeitung>Limit (category theory)DiagramTrailMotion captureSystem callSkeleton (computer programming)Operator (mathematics)SineDataflowInferenceModule (mathematics)Order (biology)Library (computing)Direction (geometry)Traffic reportingSoftware frameworkRadical (chemistry)Substitute goodVirtual machineFreewarePressureNetwork topologyData conversionWater vaporStrategy gameConfiguration spaceStructural loadAffine spaceInformation technology consultingVector spaceFiber bundleSpectrum (functional analysis)Content (media)ImplementationServer (computing)Row (database)Computer configurationCategory of beingFlow separationWordThumbnailZoom lensAttribute grammarBit rateE-bookComputer fileLocal ringComputer animation
Transcript: English(auto-generated)
So, I'm here to talk about what is certainly the most beloved and non-controversial gem
in the entire Rails ecosystem. Of course, RSpec. Yeah, exactly. Heh heh. Everybody loves RSpec, right? Right after this, Ryan's gonna come out and give you a talk just about how much he particularly loves RSpec. In the meantime, I'm gonna fulfill my role here
and set up all the things that he's going to say in the next talk. So, as I said, everybody loves RSpec. RSpec offends me aesthetically with no discernible benefit. RSpec is a cargo cult.
These also, by the way, unless you think this is a new argument in any way, shape, or form, this is from March 2011. So this is a debate, obviously, that's been going on for a long time about RSpec's complexity and its utility. And I'll just, like, I'll own it right here. Like, RSpec is complicated.
I'm not a member of the RSpec core team. I didn't write any of it. I use it all the time, but I have no ego wound up in saying that it's, or anything in particular, it's complicated. Internally, the internals of it are complicated. And while I do think that it has benefits that are discernible, and I would be happy to go over that some other time,
here we're mostly gonna talk about the complicated part. So, when we talk about RSpec being complicated, and the things that I'm gonna talk about here today are not so much the complexity of the domain-specific language that it presents to developers
who are writing tests using it, but the internal complexity of it. And RSpec, there are a couple of reasons why RSpec is complicated. I suspect the RSpec core team would talk about how expressive RSpec is, and how much trouble there is, a lot of stuff in the RSpec code base
that is there specifically to allow you to express your tests in a way that is closer to natural language than it is to straight-up Ruby. And a lot of the complexity of RSpec internals goes towards supporting that.
RSpec's also very flexible. It supports two completely different syntaxes because the deprecated version two syntax is still there. It runs in a lot of different, under a lot of different Ruby, a lot of different versions of Ruby in a lot of different contexts on a lot of different machines,
and obviously that's true of all popular frameworks in this ecosystem, but that has a certain cost in terms of the internal complexity of the code base. And it's big. It has features that other test libraries don't for good or for ill. It has them. It has a full-featured, a very full-featured mock framework attached to it,
has a very full-featured matching framework. If you're in JavaScript, you easily could be doing that with three separate libraries, and RSpec bundles them all together. Just to top off here with one quick example of how flexible RSpec is, I do want to say that with about 10 lines
of configuration, most of which are things that are exposed by the RSpec API, I was able to turn this. This is executable RSpec with my configuration. I mapped thumbs up emoji to describe, eyeball emoji to it, pointy emoji to expect,
and heart emoji to equals. Three of those four I did using stuff that is exposed by RSpec that we'll talk about in a little bit. But yeah, RSpec is, like, it's not specifically there. The flexibility is not specifically there to support emoji RSpec, but if you want to do it, it's not that hard, for good or bad.
So, this is RSpec. It's not actually magic. My name is Noel Rappin. I work at a consulting shop called Table XI in Chicago. I have stickers, and we are hiring, and if you want to talk about either of those things, you can find me. That's why I wear the green hoodie.
Hi, everybody. It's so hard. There's such a glare here that I can't really tell whether there are people out there. There are people out there, right? Okay. Hi, people. So, we're gonna be looking at some RSpec source code from the actual RSpec code base that current GitHub master has a couple days ago.
So, this is your last chance to bail before we start looking at RSpec code close up. If you have sensitive stomachs or something like that, fasten your seatbelts, make sure your tables are in their upright and locked position. Don't avert your eyes. But I do want to say,
why is it important, or why do I want to talk about the internals of RSpec? I don't... It's possible that some of you will come out of this learning a little bit more about RSpec as a user. You will probably pick up a trick or two that you could use in your own testing. That's not really why I'm doing this.
RSpec's a really interesting example of a domain-specific language written in Ruby, and it has some techniques that you might use if you want to try to write your own DSL. That's not really why I wanted to do this either. Really, I've used RSpec almost every day of my professional life for about the past eight years, and it occurred to me that I really didn't have
a serious idea of what it was actually doing. And I thought that was worth rectifying just because I was really curious and wanted to know. So that's what I'm going for here. Hopefully you'll come out of this with a little bit more understanding of RSpec's internals. So I always tell people that technical talks,
even technical talks, should tell a story. There should be a beginning, middle, and an end. My story, I guess, is once upon a time, there was a test, and we'll go from there. This is a minimal RSpec test. This is about as small as I could make it
and still have it, like, recognizably do something. And we're gonna walk through the stages that RSpec goes through to convert this first to internal representation and then to execute that representation and turn it into a pass or a fail. Now, this kind of looks like idiomatic Ruby
and it kind of doesn't. One of the things that I find often helpful when you are in Ruby exposed to a domain-specific language or something like that that is trying to look a little bit different than normal Ruby is to start to parenthesize it and fully qualify it. And if you do that, you come up with this.
This is what happens. So there are a couple things that we can see right off the bat that are very clear once we parenthesize and qualify this. We see that describe is a method that takes one argument and a block. That argument is a constant name.
We see that it is a method explicitly calling out the self receiver there. It's a method that takes one argument here and a block. And on the last line, we see that expect is a method also of whatever self is at that point. It takes whatever comes out of expect
is an object that expects to receive the method two. The method two takes as its argument something there that gets returned when I call EQ. But it's worth noting that it, expect, and EQ are all defined in terms of whatever self is
when those methods get evaluated. And those are some of the key words of RSpec's internals or RSpec's DSL. We have describe, it, expect, and then EQ being a representative example
of RSpec's matchers. And so here we have describe, it, expect to something. Each of those matches, each of those maps to an internal object or class inside RSpec. So describe maps to an RSpec concept
called an example group. It maps to an example. Expect maps to something that RSpec calls an expectation target. And then the two at the end is something that RSpec calls a matcher. If you're familiar with RSpec in terms of a user of it, you've probably heard example and matcher.
And you may not have heard example group or expectation target because those are a little bit less used by the public face of the library. So just to sort of sketch that in terms of this spec,
the describe defines the example group and the example group sort of encompasses the entire describe, the block that is the argument to describe, they all are sort of under the auspices of that example group. It, the example object is invoked by it
and it covers the block that is passed to it. Each individual called it gets its own example. The expect and its argument are the expectation target and then the matcher is that self dot EQ. So we're gonna walk through this step by step
in terms of RSpec's internals. So it starts off when RSpec executes that code, when RSpec loads it, the first thing it hits is that describe call, which creates an example group. And describe is a method, it's actually defined
somewhat indirectly as a class method of example group to create new ones. One of the things about the RSpec code base and the RSpec code base examples that I'm gonna show you is that most code in RSpec has at least one more level of indirection than you would expect. So in some cases, I'm gonna show you something
that is along the way of the RSpec call stack and not exactly the method that you think you're calling because it's actually defined in terms of something else. Describe is actually, there's no place in the RSpec code base where you'll see def describe. RSpec has a method called define example group method and then describe is defined as an instance of that
or an example of that, an alias to that. The examples to describe are the description, which is typically a string or a constant, and then RSpec's metadata, which is a set of keys or key value pairs, which we're not gonna be talking a whole lot about except that RSpec just sort of holds onto them and can use them in its execution.
And what describe, what happens when you call describe is that RSpec creates an anonymous subclass of example group and then executes the block argument in the context of that new anonymous subclass. So here's a piece of that code.
This is from the part of example group, this is sort of called along the way as that example group gets created. The first thing that happens here is you see that we're using Ruby's class.new to create a brand new class. The parent of a top level describe
is example group itself, and as you nest, they become subclasses of each successive nested example group in turn. Once you have that subclass, it calls out to a method called set it up, which you'll be surprised
to learn does some set up. Specifically it mixes in the matcher and the mock package, so if you're not using RSpec's mocks, like that's where it gets mixed in. And then RSpec uses the module exec Ruby function to execute the block, that block argument,
if it's there, and hold onto that thought for a second. And then it defines some extra helpers and then it returns that new class. Whoops, then it returns that new class. So, module exec, module exec is core Ruby, it's one of a sort of trio of module exec, class exec, and instance exec,
and those are very frequently used by domain specific language implementations to control how a block gets executed and make a block not get executed in the context where it looks like it's being defined. And specifically, module exec has the receiver, this example's actually from the Ruby core documentation,
module exec, you can see in the middle, it has a receiver thing, and then a block, and when I call module exec, that block is executed in the context of the receiver as a class. And what that means is that for the purposes of module exec, inside that block,
self resolves to that outer object. And it treats the contents of the block as though it was inside, effectively inside a class definition. So, in this case, I am defining a method, and I'm effectively reopening the thing class
and defining the method inside that so that once that module exec call is done, I can create a new thing and call that hello method on it and that will work. So, if you look at what that means in terms of what the code, the RSpec code that we are looking at, so RSpec describe
returns this example group subclass, and then we module exec that. And that block is where we have befores and afters and it's, and that block gets executed by RSpec in the context of that subclass.
So, when I say self.it, that self inside the block refers to that example group subclass, and it is a method defined on that subclass, as is before and let and all of those things
are all methods defined on example group that get executed here when RSpec does this module exec. So, we call the method it, and it,
in RSpec terms, creates an example object. There are a number of different things that are defined to be able to create example objects. Again, RSpec defines a generic method, and then defines it and specify an example as aliases to that, or as versions of that.
It takes as a description some metadata, again, and a block, and what happens when you call it is that RSpec holds onto that example object in that block, and adds it to an array that is a class attribute of that anonymous example group class
that is its parent example group. So, here's a piece of this code. You can tell that it's kind of several layers of indirection deep, because I haven't even given you the method name here, but this is part of the code that gets executed as the example gets created from the RSpec code base.
We have a bunch of stuff here that handles some bookkeeping around the metadata. This is also where, if you've specified that this example gets skipped, this is where that gets handled, but basically what happens here is that RSpec creates a new instance of this example class.
The arguments to that are self, which is the example group, the description, the metadata options, and the block. It attaches that to an examples property of the group class, and then returns the newly created example.
So, at that point, RSpec has now loaded all of the things that it needs to load. It's executed the block inside the describe. It hasn't executed the block inside the it, but it's holding onto it. So, at run time, RSpec actually creates an instance of that anonymous example group class,
and then the new instance runs all of its examples, and that happens in three stages. The example group has a method that sort of goes through all of the examples. That method spins off to a method that runs each individual example, and that method spins off to the example itself
to actually handle its own run. So, let's look at some of that code. This is a little small. I'm gonna zoom in in a second, but this is what gets called on the example group when it gets executed, and there's about three or four stages to it.
The first stage here just sets up stuff. If RSpec has already decided it wants to quit out, it just bounces right at the top. It tells the reporter or the formatter that an example group has started because the formatter might do something in response to that hook, and then it looks for context hooks
for any of its descendants. Mostly, this is mostly just bookkeeping. Inside a begin block, it then looks for, it actually runs the before method if any exist, and then that second line there, run examples, we'll zoom in on because that's where
it actually starts to run all of the examples. Once all the examples have run, that method returns a true or a false. It looks for nested children and runs all of them and determines a true or false for the entire group based on its own pass or fail status and the pass fail status of all of its children.
It's results for descendants. And then it handles some exceptions. If it skips, if there's a skip declared, it bounces out. The bottom exception here is, I believe, called if there's actually an exception in a begin block or something. It goes there and tells RSpec it wants to quit
if RSpec is in fail fast mode. That returns false because that's a failed spec. And then at the end, no matter what has happened in the ensure block, it runs all of the after hooks, and then it tells the formatter that it's done with the example group because the formatter, again, might do something in response to that.
So it's only this one line, this is supposed to be, this one line, run examples, is the thing to zoom in on to see what it actually does for each individual example. And so this is the code that actually,
that handles all those examples. The first line there, that ordering strategy order examples that it plans to run, and then it maps them. So we're gonna get a true false for every individual one of these examples by using map. And so first it bounces again
if RSpec has decided it wants to quit. It creates a new instance of the example group for each test, sets any instance for every set, and then defers off to the actual example to run it. And if that example is failed
and we're in fail fast mode, then RSpec tells itself that it wants to quit. And then it returns true false if it succeeded, if it has succeeded or failed, and uses the all method, the map will return a list of trues and falses, and the all method will return true if all of the individual examples are true.
So that's what the example group does. When the individual example gets control, it first checks to see that it's not pending and actually should run. It runs its before blocks here. I'll actually put the code up.
Checks to see, so first of all it runs any before hooks, and then it, this is the part that actually runs the test, like after, you know, we're like 45 slides into this. This is the part that actually runs the thing that we think of as the test. It takes that instance of the example group and it instance execs that block inside that group.
If it's pending, we stop, we break out. If an exception has been raised because the test failed, we hold on to that exception because we're gonna need it for reporting. And then at the end we run clean up if there's clean up,
and then we finish, which passes, whether this has passed or failed to the formatter, again, because it'll want to put a dot or an F or do whatever it does. Skeleton of what this is doing for its before blocks, but inside that it, it actually has to do something
to determine whether the spec has passed or failed, which RSpec does with expectations and with matchers. So that line of code in this test, that line of code in this test is the expect,
we're expecting something to equal something else. And that expect method call, that expect method takes in an argument which can be an arbitrary Ruby object and it returns an RSpec object called an expectation target. The expectation target, basically all it does
is it responds to two and it responds to not two to determine how it handles the matcher that it's been called with. Two and not two both take RSpec matchers as arguments and evaluate whether those matchers are actually match or don't match.
And a matcher at its most simple is just an object that responds to the API is matches question mark. So RSpec's, what we had there was two EQ, the EQ method, which is defined, EQ is one of several built-in methods that RSpec defines in a module called RSpec matchers.
There are all of these shortcut methods that basically are just really thin wrappers around these matcher objects. In this case, the matcher object is called built-in EQ. All of the things that you use as default RSpec matchers are defined in this one RSpec matchers file and most of them simply defer out
to a more complicated object that handles the actual matcher. So if we resolve both of those pieces, we wind up with an expectation target object that's using its two method and the argument there is the matcher, the EQ matcher, which also has its own argument.
The EQ matcher determines whether it matches or not by comparing whether the two values it's been called with are equal. So what happens here is that that expectation target two defers to a method that eventually calls match
for the actual matcher. In this case, it returns true. The two strings are identical. The matcher boils that up as a success because it's been done, because it's been called. Because we've used two, so we're looking for a positive expectation, it boils that up as a match and it succeeds. If we had been using not two, the matcher would still have matched,
but the expectation target would have considered that to be not the result that we were looking for and it would have raised an exception that would have gone to the reporter that the example group would have held onto for final reporting. So that's the flow, it's not really the basic anything,
but that's the workflow. RSpec creates these example groups that create examples that use these expectations and matchers to determine whether what you've stated as your expectation is actually carries.
There's one other trick that I want to talk about here for RSpec's matchers, which is the implicit matcher. So RSpec has an implicit matcher where if you do b underscore anything, so in this case, b underscore value, it defers to a predicate method of the object.
So in this case, it would look for a valid question mark on my name object. And the way that that works is it's a method missing. Here's the code inside the RSpec matchers module. There's a method missing, which is invoked because b valid is not defined anywhere.
And then the method missing compares against a regex. So we have a top, it actually looks for two different kinds of things. The b predicate regex looks for b or b a or b an. And then the has regex looks for have and just switches it around. The have regex lets you say something like,
expect something to have key rather than, and then it defers to the has key method so that your test sounds grammatical and not like a lolcat meme. The b one is what we're looking at here though. So b valid matches that b predicate, and it actually goes to a matcher,
this object built in b predicate, which takes in that method name and the arguments and the block, and in the fullness of time, comes to some code inside that matcher where it checks to see whether that predicate is accessible and whether it matches.
So it has some code that I didn't show that converts b valid to the valid question mark method. It checks to see whether that method actually exists and whether it will, sorry, it looks for it in either, it looks to see whether that method exists, and then if the method exists, it uses double underscore send to dynamically invoke it
and return true or false, whether, to determine whether the matcher passes based on whether the predicate is true or false. It uses double underscore send here because that is much, much less likely to have been overridden by the actual object, whereas it's at least theoretically possible that the object might have overridden regular send.
So the emoji trick, a brief interlude for the emoji trick, the emoji trick takes advantage of these define example group methods and define example methods that I showed before.
This is also from RSpec, this is from the internals of example group, and it's just all the different, define example group method is the method that RSpec uses to say what an example group should do when it's created, and these are all the ways that you can, by default, create an example group in RSpec. You're probably familiar with describing context, you've probably never used example group,
you may have used x describe and x context, which automatically pass along metadata to say that that example group gets skipped, or f describe and f context, which automatically pass along metadata to say that that example group has focus. You could do something similar with methods,
which you can use specify it, for some reason it fell off the slide, but specify example, f it, x it, all of which pass along metadata. So, what I did then is I just reopened example group, formatting got a little strange here, I reopened example group
and defined an example group method and an example method in terms of the emoji I wanted, and inside matches, I used the similar alias match method to use the heart emoji, and then I just sort of brute force overwrote, there's no easy way to get at an alias for x spec, so I had to kind of brute force it,
this is essentially a copy of what x spec does, and I suppose I could have used a straight alias, a regular Ruby alias, and then with that, then this code works. So, if you want to define your own emoji and really earn the love and respect of your co-workers
by doing so, it's that simple. There's one other thing I want to talk about before I left, which is I wanted to talk a little bit about how rspecs mock package works. Mocks, again, not what Ryan is going to do to rspec in the next talk, but mocks as test doubles.
So, here's a minimal mock example, we have a user, we say that we expect the user to receive a method called credit rating and return a thousand, and then we call something else that has its own expectation, but we've also set up by saying that we expect the user to receive,
we're setting up an expectation that that user will receive that method and this test will fail if that method does not get invoked. So, I wanted to think for a second just abstractly, like what has to happen in that line of code that to receive, all the bookkeeping that has to happen
for rspec, for the mock package to work. A couple of different things have to happen. Rspec needs to ensure that the underlying method is not called and that instead, the value that you've specified is returned. Rspec also has to track how often that method gets called and with what arguments because that's used to determine
whether the mock passes or fails. And then at the end of the test, it has to determine that all those expectations are met. In order to see how that's done, we need to talk about two things. First is the way that Ruby method lookup works. You may or may not be familiar with this.
Ruby method lookup first looks in the thing called the singleton class of the instance, then it goes through instance methods of that class, included modules, parent methods, parents included modules and so on. The part that I'm most interested here is the singleton class because that's the first place
that Ruby looks and it's the place that's usually not used. The singleton class, you may have seen one or two of these syntaxes. Every object in Ruby has this weird little thing called a singleton class that basically exists to define methods on. You can use that class shovel operator syntax to get at it.
You can use, you can define something with an actual instance dot method name and in both cases what happens is you create a method that only exists for that instance and no other object in your object space. Which is perfect for what a mock wants to do. A mock wants to be a method that exists for that one particular object
and no other objects in your object space. So what RSpec needs to do effectively, these are the concepts here, this is kind of a bad diagram, but we have the original object and it's singleton class and RSpec sets up this structure where RSpec has this concept of a proxy
and a method double and space. And the method double actually gets pushed into the singleton class of the original object to be the first place that Ruby will look for the method to get called and to return the value that you specified rather than the actual method. The proxy holds onto information like
how often that method has been called, what arguments and that kind of thing. And then the space is the list of all of the proxies that are defined. So if you define two separate mocked methods on the same object, this space, RSpec will use the space concept
to ensure that both of those stubs get attached to the same proxy. So let's look at a little bit of code. Because what happens here is RSpec's getting calling this as an argument to receive actually is a built-in that creates a matcher,
just like EQ did, and that matcher is defined, that matcher, just like EQ, is a very thin wrapper around an actual larger object. In this case, it's called matchers receive, and that takes in the method name being stubbed and an optional block, because most things in RSpec can take blocks,
but in this case, we don't need to worry about that block. Inside the receive matcher, there's some bookkeeping that goes on, but basically what happens is RSpec sets it up so that it creates a proxy and causes this add message expectation method to be called on it.
RSpec looks at its existing proxy space to determine if there's a proxy, to get the proxy for the object being stubbed, and then it creates this method substitute, which eventually, again, there's a couple of layers in direction here,
but eventually that add message expectation gets called. It creates this method double object and adds an expectation to it. And then that method double object holds onto the original method, holds onto reference to it, and then it uses class exec.
In this case, the definition target is the object being stubbed. Class exec gives you the singleton class of that object, and it's basically defining a method to go into that singleton class. That method has the name of the method being stubbed, and the body of it is the method double,
basically doing whatever you specified, whether you specified a return value or a block or a couple of various arcane ways that RSpec works. That method gets pushed into the singleton class so that for that object and only for that object, the stub method captures that method call
and returns the behavior that you specified and also tells the proxy that the method has been received with the arguments, because then the proxy can keep track of that. And then at the end of the test, RSpec has an after hook,
which is specifically for the mock objects, for the mock package to clean up after itself. And what it does is it walks through the space. It goes to that space object and says, are there any proxies? For each proxy, it says, do you have any methods doubles? For each methods double, it says, do you have any expectations? And for each one of those, it says,
was that fulfilled or not, given how many times this was called? And then that happens after the test runs, after all the tear downs, RSpec goes through and validates all of those and determines whether the test passes or fails. So that's a lot of stuff. I know this was a lot of code flying at you very quickly. It kind of scratches the surface.
There are a lot of cool things that RSpec does that I couldn't cover because I'm already pushing time and this was already like a freight train of code coming at you. I hope that you found this valuable. If you want more information on RSpec, the RSpec documentation, RSpec info, which is not the relish documentation
that comes off the Cucumbers, this is their straight documentation, is actually pretty decent. The code base is actually not all that hard to read once you get used to how it kind of indirect stuff. Or you can find your local RSpec core member, I happen to work with one, and pester them to tell you how things work. If you don't happen to work with an RSpec core member,
find one online and pester them. In the meantime, I'm Noel Rappin. Again, I work at Table XI. You can find me on Twitter, at Noel Rapp. I occasionally write books. I have a couple that you might be interested in. I have a book on testing Rails stuff, Rails test prescriptions.
It's really not so much about Rails as it is about testing in general through Rails. That's available at pragpocket.com. For a limited time only, and I have no idea how limited, because they didn't tell me, the code test2rapin should give you 25% off both the ebook and the physical book if you buy it through Pragmatic. Also, I do some self-publishing.
I have a JavaScript book which you can get at noelrapin.com. And I have a book called Trust Driven Development, which is about projects and estimation and how to make your clients like you, or how to behave worthy of your clients like you. Both of those are available at noelrapin.com. Both of those will be having updates and price raises in the relatively near future,
so if you want them, they will never be available for cheaper than they are available right now. Thank you for the time. I hope you found this valuable. Thanks for spending a little bit of your time with me. I hope you enjoy the rest of the conference, and I hope you will recover from my fast talk freight train of code for the last 40 minutes. Thank you.