-
-
Save patmaddox/96900b89c73c01d19a73da064b6075a3 to your computer and use it in GitHub Desktop.
# Collaboration test... | |
describe Client do | |
it 'calls a_method on a server' do | |
client = Client.new | |
server = double 'server' | |
expect(server).to receive(:a_method).with(1, 2).and_return 3 | |
client.do_it(server) | |
end | |
end | |
# For a server contract test, do you need to write: | |
# - one test for server.a_method(1, 2) | |
# - one test that server.a_method(something, something_else) returns 3 | |
# and are they the same test, or separate tests? |
Here's a bit of context on the original problem that prompted these questions...
I'm looking at a React component which uses some struct data. It expects the data in a specific structure. Imagine something like contact.address.city
. The struct data is populated by a service object which basically delegates to a REST client.
My challenge then is to:
- check that the service object provides the structure the React component expects
- check that the REST client provides the structure that the service object expects
Here's what I'm thinking...
Testing the React component
- Provide it with stub data in the structure it expects
- Assert it doesn't blow up
- Maybe assert some other stuff
This stub data represents the "answer" from the service object, which means I need a test on the service object that asserts it can return that data...
Testing the service object
- Assert the service object returns data matching the stubbed data from the react component
- Provide the service object with a mock REST client that returns necessary data structure
- Expect the service object to call the mock REST client with correct arguments
Now I have an expectation on and returned data from the REST client...
Testing the REST client
- Call the REST client with the same arguments as service object step #3, and assert that it returns the correct data
Am I on the right track?
In the case where Controller
uses Database.sum_open_order_totals()
, I imagine that the Controller
doesn't actually care about the meaning of "sum", except perhaps for the implied property that sum >= 0
. In that case, Controller
probably doesn't care about the difference between count_open_orders()
and sum_open_order_totals()
, so I would let the Controller
make the same assumptions about both: return value is a non-negative number. The Database
cares about computing the sum correctly, the feature cares about using the sum instead of a count of rows, but the Controller
probably doesn't care.
Accordingly, the Controller
stubs Database.sum_open_order_totals()
to return 0, another non-negative number, to raise an error (if that makes sense) and that's it. If sum_open_order_totals()
were really sum_open_order_totals_as_of(date)
, then I would treat it the same way we did before.
It's an open question whether Database.sum_open_order_totals()
needs "sum" to be part of its contract. I would treat this as an implementation detail until I encountered a situation where I felt doubt. What matters most is that sum_open_order_totals()
returns a value that is plausible as a sum (>= 0) and raises errors or not as the situation warrants. When we implement Database
, we'll write at least one test for sum = reduce (+) 0 (map orders quantity)
that puts in 3 orders with quantities 1, 3, 5 and expects a sum of 9. We can leave that as an implementation detail test until the contract test becomes helpful. For example, do we want to make it an explicit part of the contract that Orders have a quantity property? or do we just want to leave implicit the notion that Orders can be "summed" somehow? I prefer the latter until we judge that that's no longer good enough. Doubt, a failing test, or a customer-reported mistake would prompt me to action.
I agree that it feels off to check in a Contract Test that the orders inside the Database
are summable (even worse, that they respond to :+
! Implementation detail much?!).
Does the Controller
care how the Database
calculates the sum of the open orders? Probably not. In that case, let Database
own the notion of "sum". The Contract Tests for Database
might include a few examples of computing the sum of the open orders, but that's useful more for documentation for future clients, rather than for the current Controller
.
On To Your React Example
React Component
Add a test for what happens if the stubbed data has the wrong structure. Default values? Blow up?
For a find()
method returning at most one item, in general, I want to check this:
- returns nothing
- returns something, good format
- returns something, bad format
- returns something missing an important property (default value? blow up?)
- returns something missing an unimportant property (default value? nothing? empty string?)
- raises error
Service object
This appears to wrap a REST endpoint in order to decouple the React component from the data source. In that case:
- data source returns something valid
- data source returns something invalid
- data source returns nothing
- data source blows up
REST Endpoint
Integrated tests with server and/or VCR-style "integrated tests" with real data + fake transport
Okay so taking your more specific orders example, it might look something like:
"fetches open orders as of now"
checks thatcontroller
correctly passes'now'
todatabase.find_orders_open_as_of
, and the (super boring)"returns X for N open orders"
tests check thatcontroller
correctly counts the records returned.Splitting them up communicates a contract of:
controller
expects to pass a single argument todatabase.find_orders_open_as_of
and get an array back, but doesn't expect a specific return value when 'now' is passed in. I like that.Let's make it a tiny bit more interesting... now the
controller
will sum the total of open orders:Here I'm mainly interested in the fact that 'database.find_orders_open_as_of
returns a list of objects that respond to
total– and presumably for that
total` to respond to '+' based on the summing logic. How do you suggest I write a contract test for that? Something like...By writing my collaboration test, I've discovered that:
find_orders_open_as_of('now')
total
on themtotal
should respond to+
It feels a bit off to me that I'd be confirming all that in a single contract test.
It may be that the contract test isn't specific enough, because it doesn't follow your rules:
which would produce something closer to:
BUT my controller probably doesn't care that
total
is an integer... just that it can be summed. Would you represent that in the contract test, and if so, how? Using therespond_to
stuff from above?