5

Ok so my scenario is this: I have a third party component that offers an API for communicating with an external system. The method calls for commanding the external system to do stuff, all return void.

So what I have done is I have created a wrapper interface and have mocked out the external system. But since I have no real logic or checks in my code that calls stuff on the external API, the code is just procedural. I can't really test anything else than that the methods are being called.

_systemWrapper.DoStuff(a, b ,c , d);
_systemWrapper.DoStuff2(e, f);
_systemWrapper.DoStuff3(g);

I can't configure the external system from my tests, so integration tests are out of the question.

So what I have done to test this, is that I have mocked out my "_systemWrapper" and am just verifying that the parameters I call on the mock contain the correct parameters. Is this a common way to solve this particular problem? To me it seems to be a bit exhaustive to have a test to make sure you have this and this line of code present.

The problem is that this system is getting BIG and writing tests that assert that this and this line is present is getting out of hand, and refactoring is becoming real hard with all the tests failing if we change a call to the third party.

  • 1
    Can you elaborate on what your system does on-top of the third-party API? It's hard to imagine what kind of system lacks "real logic or checks" and still does something useful. Does the third-party api really lack any function that return information? – Winston Ewert Aug 17 '14 at 22:01
  • Yeah sure. My app is configuring a running system through this API. The original GUI for setting this system up is really terrible. So we are using the provided API for simplifying configuration of the system. What we do is that we collect a lot of information from the user in a smart way and configure the system for the user with a few mouse clicks as compared to hundreds. So in this sense, our system is really doing "all the mouseclicks" that a user previously did. Only now it's handled through the API. (which seems to be just something built ontop of an MVC controller) – hkon Aug 17 '14 at 22:13
8

There are a number of techniques for dealing with the problem of external apis. I'm going to try to describe several possible approaches that may be helpful.

Mocking

This is where your tests assert that a particular sequence of calls was executed. This is the technique that you've tried, and you discovered where it falls down. It works really well when the external api is very simple. However, for complex apis it typically becomes problematic. The tests are brittle and don't even do a good job of catching real problems.

You can help brittleness of these tests a bit, by relaxing your tests. You typically don't need to assert the exact sequence of function calls produced by a test. Perhaps your tests can allow more lee-way.

Actual Implementation

The ideal solution, in many cases, is to actually test against the real implementation of the api. That way you can assert that what you want to happen is actually happening and not worry about the exact parameters you passed to the API. As you note, "I can't configure the external system from my tests, so integration tests are out of the question." So this looks like it may not work for you.

But depending on why exactly you can't configure the external system in a test, there may still be a way to use it. One possibility is to setup your tests such that they can be run in two modes. In the first mode, they actually run against the external system, and verify that the correct things happens. However, they also record the sequence of api calls that were made. In the second, you simply verify that the same sequence of calls were made.

During normal development, you simply run tests in the second mode, on the assumption that you don't usually need to modify the calls that were made. But in the case that you refactor the code and change the calls, you re-run the tests against the external api and recapture the sequence of calls made. Then you can continue coding having your tests maintain the new sequence.

Recording the Sequence

Another possibility, is to have two modes again. In recording mode, you record the sequence of calls made to the external api. In verifying mode, you verify that you are still making the same calls. Normally, you use verifying mode. However, whenever you do need to change the sequence of calls, you rerun the tests in recording mode to capture the new sequence of calls. You can then look at the diffs between the old and new sequence to verify that the sequence still makes sense.

Use a Fake

Instead of a mock, you can implement a fake. The difference is that the fake acts like the external api. It models the external api, maintains similar internal state and/or produces the same results. Instead of verifying that the right calls are made, verify that the fake got put into the right state or provided the same result. It can verify any number of rules about what the callers of the api are allowed to do. For example, if you can't call the "foobar" method before calling the "baz" method, then the fake can throw an exception.

Fakes are very nice and flexible, but do require time to implement. Sometimes you may be able to improvise a fake by building on existing libraries/solutions. For example, a "real" SQL database can be faked by using an embedded database, such as H2.

Avoid The Api

You can try to structure your program so that as little as possible actually has to interact with the external api. This may require some clever thinking. Typically, you can reduce the amount of program that directly interacts with the problematic api to a small part.

  • Thanks for your answer I think implementing a fake is the direction from here. It will require work, but I can justify this investment. Thanks again. – hkon Aug 18 '14 at 5:29
  • Excellent overview of how to handle pesky external APIs. I took the liberty of adding an example to your answer. Feel free to roll back if you disagree. – sleske Jan 28 '17 at 17:38

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.