Different ways of testing - how would you do it?

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 contacts and contact. Gets XML from Contactio and converts it to hashes for ease-of-use internally.

  • Contact (Rails model)
    Provides update_or_create_from_hash which takes a hash from ContactioWrapper, converts it to a model and saves it.

  • SynchronizesWithContactio (class in lib)
    Uses ContactioWrapper to fetch information and tells Contact to 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

Expect that SynchronizeWithContactio calls certain methods on ContactioWrapper and Contact:

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

Pros:

  • Implementation can change without breaking tests
  • An integration test is needed anyway and this would be it

Cons:

  • Tests more than it should (the responsibility of SynchronizeWithContactio is 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

Pros:

  • Only verifies and knows about what it actually should

Cons:

  • Fragile
  • 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?

I would use both approaches together – a unit test for SynchronizesWithContactio where all the dependencies are stubbed out, and a small, high-leveel integration test to ensure everything works when all the classes are connected up.