6

I have a C# public API that is used by many third-party developers that have written custom applications on top of it. In addition, the API is used widely by internal developers.

This API wasn't written with testability in mind: most class methods aren't virtual and things weren't factored out into interfaces. In addition, there are some helper static methods.

For many reasons I can't change the design significantly without causing breaking changes for applications developed by programmers using my API. However, I'd still like to give internal and external developers using this API the chance to write unit tests and be able to mock the objects in the API.

There are several approaches that come to mind, but none of them seem great:

  1. The traditional approach would be to force developers to create a proxy class that they controlled that would talk to my API. This won't work in practice because there are now hundreds of classes, many of which are effectively strongly typed data transfer objects that would be a pain to reproduce and maintain.

  2. Force all developers using the API that want to unit test it to buy TypeMock. This seems harsh to force people to pay $300+ per developer and potentially require them to learn a different mock object tool than what their used to.

  3. Go through the entire project and make all the methods virtual. This would allow mock-ing of objects using free tools like Moq or Rhino Mocks, but it could potentially open up security risks for classes that were never meant to be derived from. Additionally this could cause breaking changes.

  4. I could create a tool that given an input assembly would output an assembly with the same namespaces, classes, and members, but would make all of the methods virtual and it would make the method body just return the default value for the return type. Then, I could ship this dummy test assembly each time I released an update to the API. Developers could then write tests for the API against the dummy assembly since it had virtual members that are very mock-able. This might work, but it seems a bit tedious to write a custom tool for this and I can't seem to find an existing one that does it well (especially that works well with generics). Furthermore, it has the complication that it requires developers to use two different assemblies that could possibly go out of date.

  5. Similar to #4, I could go through every file and add something like "#ifdef UNITTEST" to every method and body to do the equivalent of what a tool would do. This doesn't require an external tool, but it would pollute the codebase with a lot of ugly "#ifdef"'s.

Is there something else that I haven't considered that would be a good fit? Does a tool like what I mentioned in #4 already exist?

Again, the complicating factor is that this is a rather large API (hundreds of classes and ~10 files) and has existing applications using it which makes it hard to do drastic design changes.

There have been several questions on Stack Overflow that were generic about retrofitting an existing application to make it testable, but none seem to address the concerns I have (specifically in the context of a widely used API with many third-party developers). I'm also aware of "Working Effectively With Legacy Code" and think it has good advice, but I am looking for a specific .net approach given the constraints mentioned above.

UPDATE: I appreciate the answers so far. One that Patrik Hägne brought up is "why not extract interfaces?" This indeed works to a point, but has some problems such as the existing design has many cases where we take expose a concrete class. For example:

public class UserRepository 
{ 
    public UserData GetData(string userName) 
    {
        ...
    } 
}

Existing customers that are expecting the concrete class (e.g. "UserData") would break if they were given an "IUserData."

Additionally, as mentioned in the comments there are cases where we take in a class and then expose it for convenience. This could cause problems if we took in an interface and then had to expose it as a concrete class.

The biggest challenge to a significant rewrite or redesign is that there is a huge investment in the current API (thousands of hours of development and probably just as much third party training). So, while I agree that a better SOLID design rewrite or abstraction layer (that eventually could become the new API) that focused on items like the Interface Separation Principle would be a plus from a testability perspective, it'd be a large undertaking that probably can't be cost justified at the present time.

We do have testing for the current API, but it is more complicated integration testing rather than unit-testing.

Additionally, as mentioned by Chad Myers, this is question addresses a similar problem that the .NET framework itself faces in some areas.

I realize that I'm probably looking for a "silver bullet" here that doesn't exist, but all help is appreciated. The important part is protecting the huge time investments by many third party developers as well as the huge existing development to create the current API.

All answers, especially those that consider the business side of the problem, will be carefully reviewed. Thanks!

Community
  • 1
  • 1
Jeff Moser
  • 19,727
  • 6
  • 65
  • 85

6 Answers6

4

What you're really asking is, "How do I design my API with SOLID and similar principles in mind so my API plays well with others?" It's not just about testability. If your customers are having problems testing their code with yours, then they're also having problems WRITING/USING their code with yours, so this is a bigger problem than just testability.

Simply extracting interfaces will not solve the problem because it's likely your existing class interfaces (what the concrete classes expose as their methods/properties) aren't design with Interface Segregation Principle in mind, so the extracted interface would have all sorts of problems (some of which you mentioned in comment to a previous answer).

I like to call this the IHttpContext problem. ASP.NET, as you know, is very difficult to test around or with due to the "Magic Singleton Dependency" problem of HttpContext.Current. HttpContext is not mockable without fancy tricks like what TypeMock uses. Simply extracting an interface of HttpContext is not going to help that much because it's SO huge. Eventually, even IHttpContext would become a burden to test with so much so that it's almost not worth doing any more than trying to mock HttpContext itself.

Identifying object responsibilities, slicing up interfaces and interactions appropriately, and designing with Open/Closed Principle in mind is not something you and try to force/cram into an existing API designed without these principles in mind.

I hate to leave you with such a grim answer, so I'll give you one positive suggest: How's about YOU take all the grief on behalf of your customers and make some sort of service/facade layer over top of your old API. This service layer will have to deal with the minutiae and pain of your API, but will present a nice, clean, SOLID-friendly public API that your customers can use with much less friction.

This also has the added benefit of allowing you to slowly replace parts of your API and eventually make it so your new API isn't just a facade, it IS the API (and the old API is phased out).

chadmyers
  • 3,800
  • 21
  • 29
  • Very well put Chad, actually you're taking words out of my mouth, I was just about to update my earlier answer and nuance what I said a bit and give some examples but you nailed it here. I think that the service/facade layer as you put it is the way to go. – Patrik Hägne Dec 23 '08 at 09:34
  • I agree with the theory of your answer. I also agree that the .NET framework itself has some similar problems that we're facing(as you mention with HttpContext and friends).The problem that we're facing is that the existing API we have is so huge that it'd be a major undertaking to add another layer – Jeff Moser Dec 23 '08 at 11:42
  • The reason is that there are so many classes. It's a hard sell, especially in the current economy, to spend time and money to replace what we have, (even if spread it out over time). It's hard because I agree with the theory, but the business demands right now won't allow a huge systemic fix. – Jeff Moser Dec 23 '08 at 11:47
  • Testability is important and I don't want to shirk this point. We do have quite a bit of integration testing on the existing API, but we don't have the ability to easily mock collaborators with our classes and that's now causing grief, especially in the era of good mock-ing frameworks. – Jeff Moser Dec 23 '08 at 11:48
  • Jeff: I understand. So then maybe the answer is: "Just don't worry about it, then". Let them use TypeMock or something. Going forward, start incorporating SOLID principles into your design for the new stuff. – chadmyers Dec 23 '08 at 15:47
2

Another approach would be to create a seperate branch of the API and do option 3 there. Then you just maintain these two versions and deprecate the former. Merging changes from one branch into the other should work automatically most of the time.

Kim Stebel
  • 41,826
  • 12
  • 125
  • 142
  • This is the long term solution. The old API becomes a facade for the newer, correct one – chadmyers Dec 23 '08 at 06:40
  • I think that a separate branch for doing #3 would be make sense since it'd take a bit of code review. There'd still be cases where we'd want to be careful from a security perspective, but maybe there is no way around a member by member review to do it well. – Jeff Moser Dec 23 '08 at 11:55
2

As a reply to your edit, interface extraction does indeed work very well here:

public interface IUserRepository
{
    IUserData GetData(string userName);
}

public class UserRepository 
    : IUserRepository
{
    // The old method is not touched.
    public UserData GetData(string userName)
    {
        ...    
    }

    // Explicitly implement the interface method.
    IUserData IUserRepository.GetData(string userName)
    {
        return this.GetData(userName);
    }
}

As I also said in a comment this may not be the way to go in every place. I think you should identify some main points in your API where it's extra important for your customers to be able to fake the interaction and start there. You don't have to make a complete rewrite of the whole API but it can transform gradually.

Patrik Hägne
  • 16,751
  • 5
  • 52
  • 60
  • Good point on the explicit interface argument. This does add an essentially duplicated method, but it is a transition option. Also, thanks for your continued work and following up on this question. I really appreciate it! – Jeff Moser Dec 23 '08 at 13:48
0

One approach you don't mention (and the one I'd prefer in most cases) is to extract interfaces for the classes you want the user of the API to be able to fake. Not knowing your API not every single class in it has to have it's interface extracted.

Patrik Hägne
  • 16,751
  • 5
  • 52
  • 60
  • This won't really work well because the API has several methods that take in and expose a concrete class. Changing this would be a breaking change. – Jeff Moser Dec 22 '08 at 21:57
  • I'm not sure how extracting an interface could be a breaking change, you don't necessarily have to use the extracted interface in your API. It's on the clients side were he/she would expect an IFoo instead of a Foo, if you pass them a Foo from the API, that's fine. – Patrik Hägne Dec 22 '08 at 22:08
  • The trick is that existing client could would be expecting a "Foo", not an IFoo that we'd be returning, and then things break. Likewise, if the existing API had a class that took in an IFoo but (to keep the API the same) exposed a Foo property, things get messy quickly. – Aaron Lerch Dec 22 '08 at 22:09
  • Wow, ignore the bad grammar in my first sentence. :) – Aaron Lerch Dec 22 '08 at 22:10
  • Well, ofcourse method parameters would have to be changed to expect IFoo's also, but that wouldn't be a breaking change, would it? If it would, if you for example know that theres some reflection mumbo jumbo going on, just create overloads that expect the interfaces. – Patrik Hägne Dec 22 '08 at 22:11
  • To clarify Aaron's remarks, imagine you have an existing "public class UserRepository { public UserData GetData(string userName) {...} }" you could have an interface IUserRepository and IUserData, but existing customers are expecting the GetData to return a concrete "UserData" and not a IUserData. – Jeff Moser Dec 22 '08 at 22:23
  • The extracted interface would have to have something like "IUserData GetData(string userName);", but that would break existing customers. If the extracted interface was "UserData GetData(string userName);", we'd be back to the same problem. – Jeff Moser Dec 22 '08 at 22:27
  • Yes, of course, you can not change return values, but that shouldn't matter to the client when mocking. It's the interaction with the API that's mocked so he would in his code expect an IFoo, that the API returns Foo's in some cases shouldn't matter. Maybe you're talking about integration tests. – Patrik Hägne Dec 22 '08 at 22:31
  • That would work, yes. With (at least) one problematic exception: classes that accept the abstraction (IFoo) and expose it out as a concrete property: public Foo TheFoo { get; } Assuming parameter "foo", each class would need to do some sort of _Foo = foo as Foo; which is messy. – Aaron Lerch Dec 22 '08 at 22:40
  • What you're pointing out seems like an edge case Aaron, depending on the particular case, inside the class that accepts the IFoo, convert it to a Foo and use it for the property. I'm not saying this is the solution for all cases in the entire API, but it's the preferred one, where it's possible. – Patrik Hägne Dec 23 '08 at 08:40
0

Third party users should not be testing your API. They would want to test their code against your API and so they need to create Mocks of the API etc. but they would be relying on your testing of the API to ensure it works. Or is that what you meant? Do you want to make your API easy to test against?

Start again in that case, and this time think about the testers :)

Brody
  • 2,074
  • 1
  • 18
  • 23
  • Nice clarification :) Our API is used by other applications. For example, say that one of our classes returned a concrete "UserData" class and the 3rd party application took in this class and did things with it. I want them to be able to mock out the real UserData. – Jeff Moser Dec 23 '08 at 03:17
  • In short, they're not testing our API. They're testing how their code will respond when given specific data from our API. E.g. they can create a mock that fires a certain event or returns a specific piece of data back, just as if our API supplied it natively. – Jeff Moser Dec 23 '08 at 03:19
  • What you point out here is exactly why the approach of extracting interfaces works perfectly well. – Patrik Hägne Dec 23 '08 at 08:38
0

I agree with Kim. Why not re-write your core API using the best practices you explained, and supply a set of proxy/adapter classes that expose the old interface but talk to your new API?

Old developers will be naturally encouraged to migrate to the new API, but not be forced to immediately do so. New developers will simply use your new API. Announce an EOL for your old API interface if you are concerned about developers staying on the old API.

Robert Venables
  • 5,943
  • 1
  • 23
  • 35
  • It definitely sounds like a good idea. Unfortunately, the pragmatic side is hard to ignore. There is a huge investment of time and training in the old API that a rewrite is infeasible from a business perspective at the moment. – Jeff Moser Dec 29 '08 at 14:41