4

It seems there are lots of resources on how to Mock third-party code when that code is simple. A JSONRequest, etc. but when using a third-party library which requires the configuration of a relatively complex object graph – three or more classes for example – resources appear to dry up.

For example, say you been given a repository interface, IRepository, and now you're tasked with Testing and Implementing a concrete repository using Acme Inc's new AcmeBase, so you begin work creating your new AcmeRepository class.

AcmeBase's ORM requires configuring a relatively complex object graph in order to use it. The objects in the graph – while being organised well – are all quite tightly coupled:

There's the Context, the FetchRequest, the Store, the StoreCoordinator, then there are all the proprietary AcmeRecord objects themselves which of course aren't pure data objects. You could break apart the problem into multiple parts. Say, wrap the Context, FetchRequest and AcmeRecord classes, but then how do you mock an operation such as execute(request: FetchRequest) -> Set<AcmeRecord> on our Context class? All that insulation would seem to hamper the design of the framework, create a lot more work, and ironically increase the surface area for bugs.

How do you test this kind of complex coordination if you don't own the third-party classes?

It seems creating a wrapper for every class that constitutes the graph is overkill. But then how can you test that the graph has been instantiated and is being managed as you expect?

  • 1
    The techniques for mocking third-party code are the same whether you have one object in your graph or 15, though you might bring more sophisticated tools to bear on the latter case, like mocking frameworks. – Robert Harvey Aug 14 '17 at 17:21
  • 2
    Is the complexity in their API required, or do they just have a badly designed API? If the latter, write your own, better API and an adapter from theirs to yours. You'll have a bit of pain testing the adapter itself, but it'll be as limited as possible, and the rest of your code will be much easier to test. This may also apply if the library caters for a much more general/complex use-case than yours – Ben Aaronson Aug 14 '17 at 17:24
  • @robertharvey I'm using Swift, so no mocking frameworks to speak of unfortunately. And how would one handle the case of execute(request: FetchRequest) -> Set<AcmeRecord> on the hypothetical Context class anyway? Any resources for these more complex/intertwined interfaces? – Tricky Aug 14 '17 at 17:24
  • @benaaronson well written framework (Apple's), but a general use-case. I did wonder if I should just test at the boundary of the hypothetical IRepository with a MockRepository and leave it at that. – Tricky Aug 14 '17 at 17:27
3

The complexity of the library is irrelevant. What matters is how closely you've coupled yourself to the library. If you're loosely coupled you can mock it by simply controlling inputs and caching the results.

This works but you have to be willing to create a design that lets this work. Making complicated things simple isn't easy. It's work.

Don't even think of it as mocking the library. You have some needs the library can meet. Mock everything that could fulfill those needs. Don't mock what you don't need.

  • I'm not sure what you mean by "controlling inputs and caching the results."? At the top we have an IRepository and at the bottom I could create IContext, IStore interfaces which only expose the methods on the third-party libraries I wish to use – but I'm still unsure what best practice for implementing mocks for these kind of tightly coupled third-party object graphs. Are you saying there needs to be another layer in the middle in a case like this? – Tricky Aug 14 '17 at 18:27
  • I'm saying if your tests care about the complexity of the library your design is overly coupled. Doesn't mater how many interfaces you use. – candied_orange Aug 14 '17 at 18:40
  • That's the crux of my question really; I want to decouple but I'm struggling to work out the strategy of how. – Tricky Aug 14 '17 at 18:55
  • The mistake I see is that you're focusing on how the library works. Focus on your needs. 3rd party stuff shouldn't even be used until you can see how to do it yourself. Using a library should only save you typing. Not thinking. What's the simplest thing that could possibly do what you need? Code as if that exists. Then make it exist. If the library can be part of that fine. – candied_orange Aug 14 '17 at 19:05
  • Maybe something hasn't clicked for me yet. My understanding is that the Interface ('IRepository` in this example) by definition specifies my needs exactly and how I'd like it to operate, whilst the implementation should describe the how of doing that with an, unfortunately mandatory, generalised library. – Tricky Aug 14 '17 at 19:17
3

Ok, so your AcmeRepository will contain some real database code, and the business logic is already decoupled from the database by the IRepository. To test this concrete implementation, it does not make sense to "mock anything inside AcmeRepository away" - you want to test "the real thing", and if you have used the the Acme database API correctly. .

So forget about "unit testing" in the narrow sense here, or that a unit test under all circumstances must not rely on any other component. If you want to develop the thing using TDD, you need set up a test database first with the basic elements needed to run your AcmeRepository and write the tests against this DB. If you are lucky, AcmeBase will allow you to use a lightweight, fast in-memory setup for this, or it provides a "testing mode" where it just validates all API calls very quickly without much overhead. If you are unlucky, it is a monster like Oracle[put your famous heavyweight DB in here], and your test will run very slow whilst the test database management will give you a headache.

I don't think there is any shortcut to this, for third party APIs, TDD can still be applied, but only to the degree supported by the third party API.

Martin Fowler has written a nice article about the term unit tests, and he calls the kind of tests needed here "sociable unit tests", as opposed to "solitary unit tests". That article also describes the two "schools of thought", the "classicists" and the "mockists", where the latter ones insist "solitary unit tests" being the "only true kind of unit tests", whilst the former see more value in "sociable unit tests".

  • Right, so this would be an 'integration test' rather than a 'unit test'? This makes sense to me in the scenario described. I guess if we were talking about driving a peripheral, say a camera, rather than a database; something that doesn't have a testing mode in other words. That situation might call for mocks? – Tricky Aug 18 '17 at 8:22
  • @Tricky: IMHO the term integration test does not fit either, you still testing the unit AcmeRepository as a unit on its own, isolated from the business logic. I guess one could still call this "unit test", just not a unit test following the idea of being isolated from any external systems. – Doc Brown Aug 18 '17 at 8:27
  • Interesting. Lots of opinions out there. I'm researching this stuff atm – tough to get a thorough understanding. From what I've read though, we go most granular to least granular [Unit tests -> Integration Tests -> System Tests]. As this would be a test of the combination of our Interface, it's concrete implementation, and a third-party lib, it would seem to fit the definition? "the phase in software testing in which individual software modules are combined and tested as a group." In any case, its just a label, the theory seems sound. – Tricky Aug 18 '17 at 8:35
  • @Tricky: every "unit" has an interface (explicit or implicit), you always test a unit together with its interface, that does not make a test an integration test. And when I write a unit test for a module of my own, there are always third party libs involved, at least the runtime libs of the framework I am using. Nevertheless it is a unit test. – Doc Brown Aug 18 '17 at 8:45
  • 1
    @Tricky: does not look like a canonical reference for me, more like a guy who calls everything which is not a "good unit test" (according to his definition of "good") an integration test, in absence of a better word. I am here with Martin Fowler, who wrote about that term: "I see confusion can often occur when people think that it's more tightly defined than it actually is". – Doc Brown Aug 18 '17 at 9:02
0

Don't mock what you don't own. Wrap it in one of your own classes instead and mock the wrapper in your unit tests. Test the third party library through the wrapper if possible, without mocking anything, in a separate, longer-running test suite if necessary.

It turns out you already have that wrapper - it's AcmeRepository. There are numerous ways of testing data access code, whether with an in-memory version of the database or the real one. But you basically just set up test data in the DB, call one of AcmeRepository's methods and check what comes out of it or what comes into the database. It can be slow and prone to test overlap problems, but the trick is to isolate that beast away from your normal unit test suite and devise a specific test policy (test suite execution frequency, criticality, etc.) for it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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