How to Reduce Manual Effort for Wrapping Third-Party Libraries

third-party-librariesunit testingwrapper

Like the author of this question from 2012 and this one from 2013, I have a 3rd party library that I need to wrap in order to properly test my application. The top answer states:

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.

In my case, the library is for an object model and consequently has a larger number of classes and methods that would need to be wrapped for this strategy to be successful. Beyond merely "tedious and painful", this becomes a hard barrier to testing.

In the 4 years since this question, I'm aware that isolation frameworks have come a long way. My question is: does there now exist a simpler way to achieve the effect of full wrapping of 3rd party libraries? How can I take the pain out of this process and reduce the manual effort?


My question is not a duplicate of the questions I initially linked to, since my one is about reducing the manual effort of wrapping. Those other questions only ask if wrapping make sense, not how the effort can be kept small.

Best Answer

Assuming you're not looking for a mocking framework, because they're ultra-ubiquitous and easy to find, there are a few things worth noting up front:

  1. There is "never" anything you should "always" do.
    It's not always best to wrap up a third party library. If your application is intrinsically dependent on a library, or if it's literally built around one or two core libraries, don't waste your time wrapping it up. If the libraries change, your application will need to change anyway.
  2. It is OK to use integration tests.
    This is especially true around boundaries that are stable, intrinsic to your application, or cannot be easily mocked. If those conditions are met, wrapping and mocking will be complicated and tedious. In that case, I'd avoid both: don't wrap and don't mock; just write integration tests. (If automated testing is a goal.)
  3. Tools and framework cannot eliminate logical complexity.
    In principle, a tool can only cut down on boilerplate. But, there's no automate-able algorithm for taking a complex interface and making it simple -- let alone taking interface X and adapting it to suit your needs. (Only you know that algorithm!) So, while there are undoubtedly tools that can generate thin wrappers, I'd suggest that they're not already ubiquitous because, in the end, you still need to just code intelligently, and therefore manually, against the interface even if it's hidden behind a wrapper.

That said, there are tactics you can use in many languages to avoid referring directly to a class. And in some cases, you can "feign" an interface or thin wrapper that doesn't actually exist. In C#, for example, I would go one of two routes:

  1. Use a factory and implicit typing.

You can avoid the effort of fully wrapping a complex class with this little combo:

// "factory"
class PdfDocumentFactory {
  public static ExternalPDFLibraryDocument Build() {
    return new ExternalPDFLibraryDocument();
  }
}

// code that uses the factory.
class CoreBusinessEntity {
  public void DoImportantThings() {
    var doc = PdfDocumentFactory.Build();

    // ... i have no idea what your lib does, so, I'm making stuff but.
    // but, you can do whatever you want here without explicitly
    // referring to the library's actual types.
    doc.addHeader("Wee");
    doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return doc.exportBinaryStreamOrSomething();
  }
}

If you can avoid storing these objects as members, either through a more "functional" approach or by storing them in a dictionary (or whatever), this approach has the benefit of compile-time type checking without your core business entities needing to know exactly what class they're working with.

All that is required is that, at compile time, the class returned by your factory actually has the methods on it that your business object is using.

  1. Use dynamic typing.

This is in the same vein as using implicit typing, but involves another tradeoff: You lose compile-type checks and gain the ability to anonymously add external dependencies as class members and inject your dependencies.

class CoreBusinessEntity {
  dynamic Doc;

  public void InjectDoc(dynamic Doc) {
    Doc = doc;
  }

  public void DoImortantThings() {
    Doc.addHeader("Wee");
    Doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return Doc.exportBinaryStreamOrSomething();
  }
}

With both of these tactics, when it comes time to mock ExternalPDFLibraryDocument, as I stated earlier, you have some work to do -- but, it's work you'd need to do anyway. And, with this construction, you've avoided tediously defining 100's of thin little wrapper classes. You've simply used the library without looking directly at it -- for the most part.

With all of that said, there are three big reasons I would still consider explicitly wrapping up a third party library -- none of which would hint at using a tool or framework:

  1. The specific library is not intrinsic to the application.
  2. It would be very expensive to swap without wrapping it up.
  3. I don't like the API itself.

If I don't have some level concern in all three of those areas, you don't make any significant effort to wrap it up. And, if you have some concern in all three areas, an auto-generated thin wrapper isn't really going to help.

If you've decided to wrap a library up, the most efficient and effective use of your time is to build your application against the interface you want; not against an existing API.

Put another way, heed the classical advice: Postpone every decision you can. Build the "core" of your application first. Code against interfaces that will eventually do what you want, which will eventually be fulfilled by "peripheral stuff" that doesn't exist yet. Bridge the gaps as-needed.

This effort may not feel like time-saved; but if you feel you need a wrapper, this is the most efficient way to do it safely.

Think of it this way.

You need to code against this library in some dark corner of your code -- even if it's wrapped up. If you mock the library during testing, there's unavoidable manual effort there -- even if it's wrapped up. But, that doesn't mean you need to directly acknowledge that library by name throughout the bulk of your application.

TLDR

If the library is worth wrapping, use tactics to avoid widespread, direct references to your 3rd party library, but don't take shortcuts to generate thin wrappers. Build your business logic first, be deliberate about your interfaces, and emerge your adapters organically, as needed.

And, if it comes to it, don't be afraid of integration tests. They're a little fuzzier, but they still offer evidence of working code, and they can still easily be made to keep regressions at bay.

Related Topic