I am writing an app that periodically fetches data from a third-party API. The data is for a rails model
and to simplify we’ll say that it is contacts being fetched from a (fictional) provider called
Contactio. I’ve got these classes:
ContactioWrapper(class in lib)
Wraps Contactio’s API, providing methods such as
contact. Gets XML from Contactio and converts it to hashes for ease-of-use internally.
update_or_create_from_hashwhich takes a hash from
ContactioWrapper, converts it to a model and saves it.
SynchronizesWithContactio(class in lib)
ContactioWrapperto fetch information and tells
Contactto update/create the models.
Testing the first two seems straight-forward:
ContactioWrapper: Given a known XML, does it return the expected hash?
Contact: Given a known hash, does it convert to and save the expected model?
But I am not sure how I want to test
SynchronizeWithContactio. Maybe one of these:
1. Black box test (verify input and output)
Given a certain XML, does the correct model get saved?
it "saves contact successfully" do stub_contactio method: :contact, xml: some_known_xml # could use vcr contact_id = <ID from XML> synchronize_with_contactio.contact contact_id contact = Contact.find contact_id expect(contact.name).to equal "<Name from XML>" end
2. Tied to implementation
SynchronizeWithContactio calls certain methods on
it "fetches contact from Contactio and tells Contact to update it" do contact_id = double expected_hash = double expect(contactio_wrapper).to receive(:contact).with(contact_id).and_return(expected_hash) expect(Contact).to receive(:update_or_create_from_hash).with(expected_hash) synchronize_with_contactio.contact contact_id end
Thoughts on #1
- Implementation can change without breaking tests
- An integration test is needed anyway and this would be it
- Tests more than it should (the responsibility of
SynchronizeWithContactiois to orchestrate - it doesn’t know anything about XML, why is that part of the setup?)
- Doesn’t give much insight when it breaks
Thoughts on #2
- Only verifies and knows about what it actually should
- Very close to being a duplication of the implementation (especially if there are only a few code paths)
Please provide your feedback on the tests and thoughts above. What do you think about it all?