views:

54

answers:

2

Hi,

I am interacting with an API that just has static functions, and cannot be opened up and changed.

    public class WindowsNativeGraphAPI
    {
        public static IEnumerable<IGraphData> GetGraphData();
        public static bool DeleteGraphData(IGraphData data);
    }

I would like to be able to pass the API into a function or constructor and comply with dependency injection (just in case we were to swap out the API later).

public void GatherGraphData(IGraphAPI api)
{...}

To allow this API to be passed in as a parameter, I'd need to at least abstract to use an interface to pass into the function.

    public interface IGraphAPI
    {
        IEnumerable<IGraphData> GetGraphData();
        bool DeleteGraphData(IGraphData data);
    }

However, I would then need to implement the interface in another class as I cannot change the original API. This class would be a lightweight wrapper around the API that just invokes the corresponding function on the API and returns the same result.

    public class WindowsGraphAPI : IGraphAPI
    {
        public IEnumerable<IGraphData> GetGraphData()
        {
            return WindowsNativeGraphAPI.GetGraphData();
        }

        public bool DeleteGraphData(IGraphData data)
        {
            return WindowsNativeGraphAPI.DeleteGraphData(data)
        }
    }

I don't like the idea of creating another class to wrap the API. I understand that this wrapper would be very lightweight and would just return the results of the API, but how do I test the wrapper? The wrapper should probably also contain some exception handling to cope with errors in the API. If we were to change to another API, that suffered the same problem, we'd have to create these extra classes and interfaces again.

Ideally, the end result would be a mockable API that can be used when writing the unit tests for the new component that consumes it.

Is this the proper way to do this? Can it be done another way?

Thanks

+5  A: 

Yes, it's the proper way. The new API interface and proxy class encapsulate the decision of what underlying library to use - a single responsibility.

Pontus Gagge
+1 ASP.NET MVC does this extensively, so it provides a set of examples that demonstrates this approach.
Mark Seemann
A: 

Yes that is the proper way. I would not put exception handling in your wrapper, all you are doing is creating a class that calls static methods so you can use DI. You want the wrapper to cause the same exceptions under the same circumstances as the API class with the static methods. That way you can use the same exception handling in your method as you would if you were called the class with the static methods. You can then throw the same exceptions under the same circumstances in your mock api class and test the excpetion handling.

Ben Robinson
I can't agree with these statements about exceptions. Exceptions are part of the functional contract of an API. If the exception types thrown by the adapted API are defined in the package that contains the adapted API itself, allowing those exceptions through would let implementation details leak through the abstraction. Better to wrap them in exceptions that are defined together with the interface.
Mark Seemann
That sounds fantastic from an accademic point of view, but in practice you either end up with insuficient detail in your abstracted exceptions that majke debugging problems in live code a pain in the arse or you have to do a load of work creating complex exception handling to provide enough detail to debug later, simply to account for that fact that you MAY at some point change the implemenation entirely rather than just mocking the windows API for unit testing.
Ben Robinson
I'm not saying that the wrapper *must* wrap the exceptions thrown by the adapted API. What I'm saying is that the client *must not* rely on exception types defined by the adapted API, as that would create a tight coupling. DI is so much more than testability.
Mark Seemann
I can see your point but you would have to do it very carefully, making sure that all the relevent info is logged and handled corectly. I have just spent too much time wishing that exceptions from some base API were coming through into a log file in unadulterated, because without them im having an all but impossible time trying to work out why some code doesn't work.
Ben Robinson
@Ben: having a nested exception member together with decent logging transforms this from academic to eminently practical maintainability.
Pontus Gagge