6

Where is the figurative line drawn for using static services in a project? I am a coop student working and learning how to write .net MVC projects. I've been developing trying to stick to TDD. In my project I'm using ninject for dependency injection. I have written an abstract and a class implementing that for making API service calls to our internal API. There is no class library for these APIs yet so I'm writing my own stuff.

In this case I'm finding myself wondering why I am using non-static classes for this API service. There is no state information stored for API calls they are IP validated. All the addresses for call locations are stored in the config file.

I can't see how using a static class here would make testability harder. You can unit test the static class to see that it works. If you find yourself needing mock data you could simply mock the object the API is supposed to return. If that is impossible because of your method design it is highly likely the method you are testing is trying to do to much.

I feel that almost all situations, in this particular case, where the fact the service is static would affect testing would arise when trying to manipulate the returned objects; which should be abstracted out to an extension or utility method that itself could be tested, so there should be no issue with the static class affecting testing.

Is there a good reason to not go static that I am just missing, or something about unit testing and injection that I am just missing? I would like to be able to confidently say using static is a sound idea in this case, but feel am I lacking the knowledge to do so.

Another question was linked 'Is static universally evil' that is not my question. I know static isn't universally evil and has its place. My question is where is it prudent to use static and under what circumstances might an abstract service be better written as a static one, while not causing testability issues.

  • So using a static service would prevent me from making a true unit test? I would have to presume that because all the test pass the composite of the test would pass? Or do a scenario test but not a true unit test on methods using static services? – Tenderdude Nov 23 '16 at 19:22
  • looks like you didn't read it fully: this depends on the language. In Java this would be the issue, in Groovy - not – gnat Nov 23 '16 at 19:23
  • Ah yes sorry I'm currently working so I read down to why how groovy uses expandometaobjects to get around it and stopped there. I am in c# so I assume it would have much the same problems as java. – Tenderdude Nov 23 '16 at 19:26
  • 2
9

Static methods are problematic because they prevent dependency injection for users of that static method.

First, let me be clear that this is not always an issue. If a function is pure (maintains no state across calls, and does not perform any I/O), then you can just test it directly once, and then use it in other code knowing that it will work. This usually only applies to small utility methods, or when you're doing strict functional programming.

If the service provided by that static method is more complex, using dependency injection techniques becomes desirable:

  • If a method maintains state, you do not want to keep that state between different tests – that would be a kind of coupling that could obscure bugs. At the very least, you will want a mechanism to (at least temporarily) reset the state for each test. The easiest way to do that is to create a new instance of the enclosing class, which is not possible with static methods and static classes.

  • If the service interacts with the outside world or performs any I/O (aside from logging), you don't want to do that during an unit test.

    Let's consider a service to send email notifications. The system under test should send emails under certain conditions. How do you test this? Set up a test email account, and verify that you got the expected message? Yes, but that would be an integration test. For unit tests, we would just want to make sure that the email service was invoked with the correct configuration.

    If you can intercept the call through some other mechanism than replacing the service through dependency injection, that's probably OK as well.

  • If the service contains substantial business logic that might change frequently, you do not want to have unrelated tests fail because of changes to that logic. In the scope of a test, replacing unrelated business logic with stubs will make your tests more robust – assuming it is of course tested elsewhere.

Static classes do have their uses, but they should not be your default choice. Only when it's clearly not desirable to have an instance of that class should you make it static, which pretty much only happens when it's less of a class and more of a namespace for static methods.

  • Thank you this makes things clearer. I guess the 'service' I was thinking about was more of a utility like class making a simple request to a url and deserializing it into an object. No real business logic at all. I might Implement that within a service and that service should almost definitely not be static though. – Tenderdude Nov 23 '16 at 19:59
  • Also I don't think in my testing I have been making the proper distinction between a unit test and an integration test. Thank you for the concise bullet points. Still learning how to best follow design guidelines. – Tenderdude Nov 23 '16 at 20:10
0

Testing static classes can be done with a trick: design your static class such that it is actually some kind of wrapper around an instance: all calls to the static class are forwarded to an instance which actually does the job. Then add a static method void SetInstanceForTesting(IInterfaceOfTheInstance instance). I got that idea from Mike Feather's book on legacy code.

  • 4
    As you mentioned yourself, the book is about dealing with legacy code. While this is a useful technique for providing tests to otherwise not (unit-)testable code, it is not a good idea to write new code that requires such hacks to be testable. – Hulk Nov 25 '16 at 9:18

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.