13

I'm doing unit testing and in one of my classes I need to send a mail from one of the methods, so using constructor injection I inject an instance of Zend_Mail class which is in Zend framework.

Now some people argue that if a library is stable enough and won't change often then there is no need to wrap it. So assuming that Zend_Mail is stable and won't change and it fits my needs entirely, then I won't need a wrapper for it.

Now take a look at my class Logger that depends on Zend_Mail:

class Logger{
    private $mailer;    
    function __construct(Zend_Mail $mail){
        $this->mail=$mail;
    }    
   function toBeTestedFunction(){
      //Some code
      $this->mail->setTo('some value');
      $this->mail->setSubject('some value');
      $this->mail->setBody('some value');
      $this->mail->send();
     //Some
   }        
}

However, Unit testing demands that I test one component at a time, so I need to mock the Zend_Mail class. In addition I'm violating the Dependency Inversion principle as my Logger class now depends on concretion not abstraction.

Now how can I test Logger in isolation without wrapping Zend_Mail?!

The code is in PHP, but answers doesn't have to be. This is more of a design issue than a language specific feature

Songo
  • 6,548
  • 4
  • 48
  • 89
  • Do you have to use an interface? Doesn't PHP support duck typing? – kevin cline Nov 05 '12 at 22:42
  • @kevincline well I used PHP because it's the language that I use most, but I'm actually looking for a general solution to problem not limited to PHP only. – Songo Nov 05 '12 at 22:53

1 Answers1

22

You always want to wrap third party types and methods behind an interface. This can be tedious and painful. Sometimes you can write a code generator or use a tool to do this.

But don't be tempted to use the library methods or types throughout your code. To begin with, you will have trouble writing unit tests. Then a license will change, or you'll want to go to a platform not supported by the third party, and you will find that those types and dependencies have woven in and around all your other classes.

The ability to change third party vendors quickly is a huge advantage.

Ben
  • 1,594
  • 12
  • 14
  • so I should wrap anything that isn't written by me? should these wrappers be unit tested themselves? as you can see my wrapper class instantiate `Zend_Mail` itself in its constructor, so testing it will be like testing `Zend_Mail` itself?! – Songo Nov 05 '12 at 22:05
  • 5
    "Anything not written by you" is a bit much. Libraries that are part of the standard or platform are difficult to wrap. You probably wouldn't want to wrap all of the .NET components, for example. If the wrappers are just pass through interfaces, or are generated code, I have found little benefit to writing tests. If there is logic in there (combining calls, etc.) tests can be helpful. – Ben Nov 05 '12 at 22:14
  • 4
    Upvoted for the last sentence. – Blrfl Nov 05 '12 at 22:20
  • @Ben well I'm not an expert in .Net, but as I recall there was a class `File` built-in the .Net framework. If one of my classes needs a `File` in its constructor then I should wrap it to be able to mock it right. This is the way you would do it right? – Songo Nov 05 '12 at 22:35
  • 1
    If you refactor properly, any repetitive use of library facilities will be factored out into a service class. No need to define it up front. – kevin cline Nov 05 '12 at 22:42
  • 4
    -1: Except in cases where the third party library provides a service for which there is a standardized API, this is a huge waste of time andwill only reduce maintainability by having you duplicate code. Also, YAGNI. – Michael Borgwardt Nov 07 '12 at 09:45
  • 1
    @MichaelBorgwardt: Sure, but in that case, the standard API becomes the wrapper and you can swap libraries easily. – Blrfl Nov 07 '12 at 21:42
  • -1, though the idea sounds good at a first glance, I know too many examples where this approach is completely unrealistic. Ben's example (the .NET framework) is just one typical case of many. – Doc Brown May 20 '15 at 14:55