16

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.

  • What programming language, and what kind of library are you talking of? – Doc Brown Dec 12 '16 at 20:47
  • @DocBrown C# and a PDF manipulation library. – Tom Wright Dec 13 '16 at 8:28
  • 2
    I started a post on meta to find some support for reopening your question. – Doc Brown Dec 13 '16 at 8:48
  • Thanks @DocBrown - I'm hopeful that there'll be some interesting perspectives out there. – Tom Wright Dec 13 '16 at 9:21
  • 1
    When we don't get better answers the next 48 hours, I will place a bounty on this. – Doc Brown Dec 14 '16 at 12:54
4
+100

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.

  • 2
    This is another post not answering the question - which was explicitly not "when to wrap", but "how to reduce the manual effort". – Doc Brown Dec 19 '16 at 22:47
  • 1
    Sorry, but I think you missed the core point of the question. I don't see how your suggestions could reduce any manual effort in wrapping. Assume the lib in stake has a complex object model as API, with some dozens classes and hundreds of methods. Its API is fine as it is for standard usage, but how to wrap it for unit testing with less pain/effort? – Doc Brown Dec 20 '16 at 5:43
  • 1
    TLDR; if you want the bonus points, tell us something we don't know already ;-) – Doc Brown Dec 20 '16 at 5:59
  • 1
    @DocBrown I didn't miss the point. But, I think you missed the point of my answer. Investing the right effort saves you a lot of work. There are testing implications -- but that's just a side effect. Using a tool to automagically generate a thin wrapper around a library still leaves you building your core library around someone else's API and building mocks -- which is a lot of effort that can't be avoided if you're insistent upon leaving the library out of your tests. – svidgen Dec 20 '16 at 14:58
  • 1
    @DocBrown This is absurd. "Does this class of problems have a class of tools or approaches?" Yes. Of course it does. Defensive coding and not getting dogmatic about unit tests ... like my answer says. If you did have an automagically generated thin wrapper, what value would it provide!? ... It wouldn't permit you to inject dependencies for testing, you still have to do that manually. And it wouldn't allow you to swap the library out, because you're still coding against the library's API... it's just indirect now. – svidgen Dec 20 '16 at 15:29
9

Don't unit test that code. Write integration tests instead. In some cases, unit testing, mocking, is tedious and painful. Ditch the unit tests and write integration tests that actually make vendor call outs.

Note, these tests should be run after the deployment as a post deployment automated activity. They are not run as part of unit tests or part of the build process.

Sometimes an integration test is a better fit than a unit test in certain parts of your application. The hoops one must go through to make the code "testable" can sometimes be detrimental.

  • 2
    First thing I thought when I read your answer was "unrealistic for PDF, since comparing the files on a binary level does not tell you what changed or if the change is problematic". Then I found this older SO post, indicates it actually might work (so +1). – Doc Brown Dec 13 '16 at 12:43
  • @DocBrown the tool in the SO link doesn't compare the PDF's in a binary level. It compares the structure and the contents of the pages. Pdf internal structure : safaribooksonline.com/library/view/pdf-explained/9781449321581/… – linuxunil Dec 20 '16 at 11:08
  • 1
    @linuxunil: yes, I know, as I wrote, first I thought ... but then I found the solution mentioned above. – Doc Brown Dec 20 '16 at 11:33
  • oh.. i see.. maybe the 'it actually might work' in the final of your answer got me confused. – linuxunil Dec 20 '16 at 13:34
1

As I understand, this discussion focuses on opportunities for wrapper creation automation rather than wrapping idea and implementation guideline. I will try to abstract from the idea as there is already a lot about it here.

I see that we are playing around .NET technologies, therefore we have powerful reflection capabilities in our hands. You may consider:

  1. Tool like .NET Wrapper Class Generator. I haven't used that tool and I know that it operates on a legacy technology stack, but maybe for your case it would fit. Of course, code quality, support for Dependency Inversion and Interfaces Segregation should be investigated separately. Maybe there are other tools like that but I didn't search a lot.
  2. Writing your own tool which will search in referenced assembly for public methods / interfaces and does the mapping and code generation. Contribution to the community would be more than welcome!
  3. If .NET is not a case ... maybe look here.

I believe that such automation may constitute a base point, but not a final solution. Manual code refactoring will be required ... or in worst case a redesign as I completely agree with what svidgen wrote:

Build your application against the interface you want; not against a 3rd party API.

  • Ok, at least an idea how the problem might be handled. But not based on own experience for this use case, I assume? – Doc Brown Dec 20 '16 at 11:48
  • The question is based on my own experience regarding software engineering domain (wrapper, reflection, di)! Regarding the tools - I didn't used that as stated in the asnwer. – tom3k Dec 20 '16 at 12:11
0

Follow these guidelines when creating wrapper libraries:

  • expose only a small subset of the third party library that you need right now (and expand on demand)
  • keep the wrapper as thin as possible (no logic belongs there).
  • 1
    Just wondering why you got 3 upvotes for a post which misses the point of the question. – Doc Brown Dec 20 '16 at 15:15

protected by gnat Dec 20 '16 at 12:05

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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