Configuring Responses

Since a test double is meant to stand in for a real object, it needs to act like one. You need to be able to configure how it responds to the code calling it.

When you allow or expect a message on a test double without specifying how it responds, RSpec provides a simple implementation that just returns nil. Your test doubles will often need to do something more interesting: return a given value, raise an error, yield to a block, or throw a symbol. RSpec provides ways for your doubles to do each of these:

 allow​(double).to receive(​:a_message​).and_return(a_return_value)
 allow​(double).to receive(​:a_message​).and_raise(AnException)
 allow​(double).to receive(​:a_message​).and_yield(a_value_to_a_block)
 allow​(double).to receive(​:a_message​).and_throw(​:a_symbol​, optional_value)
 allow​(double).to receive(​:a_message​) { |arg| do_something_with(arg) }
 
 # These last two are just for partial doubles:
 allow​(object).to receive(​:a_message​).and_call_original
 allow​(object).to receive(​:a_message​).and_wrap_original { |original| }

Now, let’s look at a couple of specific situations you may run into when you’re specifying how your test doubles will behave.

Method Expectations Replace Their Originals

images/aside-icons/info.png

People new to RSpec are often surprised at the behavior of expect on a partial double. The following code:

 expect​(some_existing_object).to receive(​:a_message​)

…doesn’t just set up an expectation. It also changes the behavior of the existing object. Calls to some_existing_object.a_message will return nil and do nothing else. If you want to add a message expectation while retaining the original implementation, you’ll need to use and_call_original.

Returning Multiple Values

You’ve already used and_return, in Test Doubles: Mocks, Stubs, and Others. There, you set up your test double to return the same canned expense item each time it received the record message.

Sometimes, you need your stubbed method to do something more sophisticated than return the same value every time it’s called. You might want to return one value for the first call, a different one for the second call, and so on.

For these cases, you can pass multiple values to and_return:

 >>​ ​allow​(random).to receive(​:rand​).and_return(0.1, 0.2, 0.3)
 => #<RSpec::Mocks::MessageExpectation #<Double "Random">.rand(any arguments)>
 >>​ random.rand
 => 0.1
 >>​ random.rand
 => 0.2
 >>​ random.rand
 => 0.3
 >>​ random.rand
 => 0.3
 >>​ random.rand
 => 0.3

Here, we give three return values, and the rand method returns each one in sequence. After the third call, the method continues to return 0.3, the final value passed to and_return.

Yielding Multiple Values

Blocks are ubiquitous in Ruby, and sometimes your test doubles will need to stand in for an interface that uses blocks. The aptly named and_yield method will configure your double to yield values.

To specify a sequence of values to yield, chain together multiple calls to and_yield:

 extractor = double(​'TwitterURLExtractor'​)
 
 allow​(extractor).to receive(​:extract_urls_from_twitter_firehose​)
  .and_yield(​'https://rspec.info/'​, 93284234987)
  .and_yield(​'https://github.com/'​, 43984523459)
  .and_yield(​'https://pragprog.com/'​, 33745639845)

We’ve chained together three and_yield calls. When the code we’re testing calls extract_urls_from_twitter_firehose with a block, the method will yield to the block three times. Each time, the block will receive a URL and a numeric tweet ID.

Raising Exceptions Flexibly

When you’re testing exception-handling code, you can raise exceptions from your test doubles using the and_raise modifier. This method has a flexible API that mirrors Ruby’s raise method.[98] That means all of the following calls will work:

 allow​(dbl).to receive(​:msg​).and_raise(AnExceptionClass)
 allow​(dbl).to receive(​:msg​).and_raise(​'an error message'​)
 allow​(dbl).to receive(​:msg​).and_raise(AnExceptionClass, ​'with a message'​)
 
 an_exception_instance = AnExceptionClass.new
 allow​(dbl).to receive(​:msg​).and_raise(an_exception_instance)

In the examples we’ve shown you so far, we’ve been working with pure test doubles. These doubles have to be told exactly how to respond, because they don’t have an existing implementation to modify.

Partial doubles are different. Since they begin as a real object with real method implementations, you can base the fake version on the real one. Let’s look at how to do so.

Falling Back to the Original Implementation

When you’re using a partial double to replace a method, sometimes you only want to replace it conditionally. You may want to use a fake implementation for certain parameter values but fall back on the real method the rest of the time. In these cases, you can expect or allow twice: once like you normally would, and once with and_call_original to provide the default behavior.

 # fake implementation for specific arguments:
 allow​(File).to receive(​:read​).with(​'/etc/passwd'​).and_raise(​'HAHA NOPE'​)
 
 # fallback:
 allow​(File).to receive(​:read​).and_call_original

Here, we’ve used with(...) to constrain which parameter values this stub applies to. We’ll talk more about with later in Constraining Arguments.

Modifying the Return Value

Sometimes, you want to slightly change the behavior of the method you’re stubbing, rather than replacing it outright. You may, for instance, need to modify its return value.

To do so, call RSpec’s and_wrap_original method, passing it a block containing your custom behavior. Your block will take the original implementation as an argument, which you can call at any time.

Here, we use this technique to stub out a CustomerService API to return a subset of customers:

 allow​(CustomerService).to receive(​:all​).and_wrap_original ​do​ |original|
  all_customers = original.call
  all_customers.sort_by(&​:id​).take(10)
 end

This technique can be handy for acceptance specs, where you want to test against a live service. If the vendor doesn’t provide a test API that only returns a few records, you can call the real API and narrow down the records yourself. By working on just a subset of the data, your specs will remain snappy.

Tweaking Arguments

You can also use and_wrap_original to tweak the arguments you pass into a method. This technique comes in handy when the code you’re testing uses a lot of hard-coded values.

In Stubbed Constants, we used stub_const to call a hashing algorithm with a lower cost factor so that our specs kept running quickly. That approach only worked because the cost was defined as a constant.

If instead the number had been a hard-coded argument value, we could have overridden it using and_wrap_original:

 allow​(PasswordHash).to receive(​:hash_password​)
  .and_wrap_original ​do​ |original, cost_factor|
  original.call(1)
 end

If the method you’re stubbing takes arguments (such as the cost_factor), RSpec passes these as additional parameters into your block.

Since both and_call_original and and_wrap_original need an existing implementation to call, they only make sense for partial doubles.

When You Need More Flexibility

So far, we’ve seen several different ways to customize how your test doubles behave. You can return or yield a specific sequence of values, raise an exception, and so on.

Sometimes, though, the behavior you need is slightly outside what these techniques provide. If you’re not quite sure how to configure a double to do what you need, you can supply a block containing whatever custom behavior you need. Simply pass the block to the last method call in the receive expression.

For instance, you might want to simulate an intermittent network failure while you’re testing. Here’s an example of a weather API test double that succeeds 75 percent of the time:

 counter = 0
 
 allow​(weather_api).to receive(​:temperature​) ​do​ |zip_code|
  counter = (counter + 1) % 4
  counter.zero? ? ​raise​(Timeout::Error) : 35.0
 end

When your code calls weather_api.temperature(some_zip_code), RSpec will run this block and, depending on how many calls you’ve made, either return a value or raise a timeout exception.

Don’t Get Carried Away with Blocks

images/aside-icons/info.png

If your block gets any more complex than the weather API example here, you might be better off moving it into its own Ruby class. Martin Fowler refers to this kind of stand-in as a fake.[99] Fakes are particularly useful when you need to preserve state across multiple method calls.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset