require'spec_helper'require'logger'require'stringio'describe'Running the server locally'doit'logs that it is running'doio=StringIO.newallow(Logger).toreceive(:new).and_return(Logger.newio)expect{load'script/local-server'}.tochange{io.string}.toinclude'SERVER: starting up'endend
However, when the script is run standalone, it errors with:
1
uninitializedconstantLogger(NameError)
Be aware that since load
happens in the current spec context, a missing require may not be noticed if
it is required by the spec.
Whenever possible have at least one test that shells out as a sanity check.
This works just fine as a normal matcher, however, when used as an argument
matcher, it will always fail. The reason is that argument matchers are invoked
with the == operator, which by default,
verifies if the objects are the same object.
Attempting to use a normal matcher with the
change
expectation also
oddly fails, due to
change invoking the
===
message, not matches?. Since the default === behavior is
==, the existing
argument matchers currently work with it.
There is active talk / changes
happening to
standardize the matchers to ===. This will allow for a more consistent and
composable interface. It also has the added benefit of allowing the matchers to
be used in more complex conditionals using case statements.
To fix the class based matcher simply add the necessary alias(es):
1234567
classSomethingBrokendefmatches?(target)target.broken?endalias_method:==,:matches?alias_method:===,:matches?# Not technically necessary due to default ==end
Note that with such a simple matcher, there is no reason it cannot be created
as a simple composed method using an existing matcher:
123
defsomething_brokenbe_broken# Note: Not all built in matchers have == aliases yetend
Check out the full Monitoring Messages in Rspec series:
part 1,
part 2,
part 3
Last time it was demonstrated how it is possible to monitor only a desired
message expectations. While that technique is useful for a vast majority of
use cases, sometimes you need a bit more complexity.
Say for example, the desire is to verify the state of the provided parameter.
As in this very contrived example (Update: 2013-07-28 Paired down example to
only show test and not implementation):
1234567891011121314
describeFactorydocontext'making sure current stock is ready'doit'only fixes broken widgets'do# Setup codestates=[]expect(mechanic).toreceive(:fix)do|widget|states<<widget.broken?endexpect{factory.perform_maintenance}.tochange{states}.to[true]endendend
While the above version has it’s uses, it tends to hide some of the intent in
the closure manipulation. A slightly more expressive method is just to add
the state expectation in the block:
This last technique is great for setting up generic message stubs which require
that something with specific state is provided. By adding it to a generic
allow, it ensures when the contract is broken anywhere in the code under test,
the test will properly fail. (Update 2013-07-30: seems there is an
issue with this technique when
combined with
and_call_original)
Check out the full Monitoring Messages in Rspec series:
part 1,
part 2,
part 3
A common question I see asked in the #rspec IRC channel is:
How do I verify only a specific message is received?
The context of this question is usually a method that sends the same message
multiple time, but with different arguments.
Take the following contrived example:
123456789101112131415
defdo_stuff(thing)thing.do:having_funthing.do:drink_coffeething.do:codeenddescribe'Coffee time!'doit'drinks the coffee'dothing=double('Thing')expect(thing).toreceive(:do).with(:drink_coffee)do_stuffthingendend
Alas, it fails:
123
Double "Thing" received :do with unexpected arguments
expected: (:drink_coffee)
got: (:having_fun)
The solution is to add a generic stub (a catchall) for the desired message:
12345678910
describe'Coffee time!'doit'drinks the coffee'dothing=double('Thing')allow(thing).toreceive(:do)expect(thing).toreceive(:do).with(:drink_coffee)do_stuffthingendend
There is another solution which can also be used:
as_null_object.
The issue with as_null_object is that it will happily hum along even for
invalid and unexpected messages. The double stub pattern above is
explicit in the test, matching the catchall only with the message expectation.
123456789
describe'Coffee time!'doit'drinks the coffee'dothing=double('Thing').as_null_objectexpect(thing).toreceive(:do).with(:drink_coffee)do_stuffthingendend
Stay tuned for RSpec3
when as_null_object doubles will be smart enough to only stub matching
messages. Or check out one of the many plugins:
rspec-fire,
bogus, and
spy.
Check out the full Testing Scripts with Load series:
part 1,
part 2,
part 3
A standard method for testing Ruby scripts is to shell out to the script using
Kernel#`
or Kernel#system.
Both of these follow a fork
and exec pattern to
run the command.
However, there are instances where it is necessary to mock/stub something in
the script. The above technique fails in this situation due to exec
replacing the current process; the stubs simply cease to exist.
# Specdescribe'Monitoring cpu heat'doit'uploads the temperature to the server'dostub_const('ARGV',['monitor-integration.bak'])stub_request(:put,'https://api.server.com/temp').to_return(status:200)expect(load'script/monitor').tohave_requested(:put,'https://api.server.com/temp').with(body:/\A{"temp":\d+}\z/)endend
In conclusion, no more excuses for not have integration tests for your scripts.