5
\$\begingroup\$

So I'm writing an abstraction layer that wraps a telephony RESTful service for sending text messages and making phone calls. I should build this in such a way that the low-level provider, in this case Twilio, can be easily swapped without having to re-code the higher level interactions.

I'm using a package that is pre-built for Twilio and so I'm thinking that I need to create a wrapper interface to standardize the interaction between the Twilio service package and my application. Let us pretend that I cannot modify this pre-built package.

<?php

namespace Telephony;

class Provider_Twilio implements Provider_Interface {

    public function send_sms(Provider_Request_SMS $request) {

        if (!$request->is_valid())
            throw new Provider_Exception_InvalidRequest();

        $sms = \Twilio\Twilio::request('SmsMessage');
        $response = $sms->create(array(
            'To' => $request->to,
            'From' => $request->from,
            'Body' => $request->body
        ));

        if ($this->_did_request_fail($response)) {
            throw new Provider_Exception_RequestFailed($response->message);
        }

        $response = new Provider_Response_SMS(TRUE);
        return $response;

    }

    private function _did_request_fail($api_response) {
        return isset($api_response->status);
    }

}

The idea is that I can write another file like this for any other telephony service provided that it implements Provider_Interface making them swappable. Here are my questions:

  1. Do you think this is a good design? How could it be improved?
  2. I'm having a hard time testing this because I need to mock out the Twilio package so that I'm not actually depending on Twilio's API for my tests to pass or fail. Do you see any strategy for mocking this out?
\$\endgroup\$
0

1 Answer 1

5
\$\begingroup\$

In circumstances like you have here; the almost lowest level layer wrapping a dependency, it can be hard to see another layer lower to allow abstraction, but remember this: The lowest level wrapper of a dependency is a literal straight pass-through.

This means, if you take the twilio api and create a direct correlary that simply passes-through everything to the twilio api, then that wrapper is the lowest level wrapper and therefore bears no need of testing at the unit level (testing at the integration and build-verification levels however is worth while) as it has no actual functionality.

If you create such a wrapper, you may then pass in a mock of it to your next-level-up which is your provider here.

So i guess there are two rules I'm stating here, and glad to hear people give points for or against:

  • The lowest level wrapper possible of external dependencies is a direct pass-through (which feels stupid and tedious when writing it, but actually serves a purpose)
  • The lowest level wrapper of an external dependency has no need of testing at the unit level as it maintains no testable functionality, but should be tested at the integration and build-verification levels as well as all external dependencies should.

Also, for your particular case and similar ones, you have an obvious default implementation of the dependency that you will be wrapping so feel free to have a default constructor so its consumer's do not need to construct the twilio wrapper, while maintaining the ability for the twilio wrapper to be settable for the unit tests. Sometimes making the accessibility of these setters protected enough that consumers will not think it's ok to change it, while accessible enough for proper unit testing can be tricky; I tend to treat those situations with a use your best-judgement approach.

Edit: I would unit test the Provider_Interface. Though that's one man's opinion, many people would say unit testing at this level is stupid, I maintain that where there is functionality, which your Provider_Interface has, there may be bugs. You could have a typo in 'SmsMessage' for instance, and therefore should unit test for these things. However I acknowledge there is much disagreement in industry about the ROI on unit testing at this level, or maintaining pass-through wrappers for such purpose as I am advocating.

\$\endgroup\$
4
  • \$\begingroup\$ So what you're saying is that unit testing the low-level wrapper around the Twilio package is not necessary, but I should unit test the layer that uses Provider_Interface instead? \$\endgroup\$ Sep 3, 2012 at 19:25
  • \$\begingroup\$ @JesseBunch You're almost there; you missed the part about the lowest level wrapper is a pass-through which the code listed here is not. a pass-through has zero functionality, merely mimicing the external dependencie's API so that said API can be faked. Said pass-through would not check inputs or do any form of functionality whatsoever. This is where I was saying, it's hard to see there is another layer lower to allow abstraction, one below what you have. \$\endgroup\$ Sep 3, 2012 at 19:28
  • \$\begingroup\$ Gotcha. So, since I cannot modify the Twilio Package, I should create the pass-thru wrapper which can then be mocked and thus responses can be faked. Did I get it this time? \$\endgroup\$ Sep 3, 2012 at 19:41
  • \$\begingroup\$ @JesseBunch Yep :) \$\endgroup\$ Sep 3, 2012 at 20:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.