When I interview people I often ask them what an evil developer would do to make his code hard to test. Servlets are a great example of what to do if you want it to be hard to test
Lets start with constructor. Constructor is the first thing I look at since it tells me about my dependencies. In the case of servlets the spec asks for no-argument constructor so that the container can control the instantiation process. This is all fine but how is my Servlet supposed to get a hold of its dependencies such as database connection or any other object which needs to be shared between multiple servlets? There is no good way! The only thing which I can do is to use global state and singletons for inter servlet communication (which we have covered in detailed here, here, and here).
Initialization process is no help here. Yes the container will call a init method, but the only thing I can get a hold of in the initialization process is the strings which I have placed in my XML. What I really want is to share instances of objects between servlets so that I can configure my collaborators. Again I am forced to result to global state and singletons. But there is something worse about this. Mainly the single responsibility principle. Each class is responsible for exactly one thing. But in our case the servlet is responsible for the logic, the wiring of itself to the collaborators and initialization of the collaborators. Now, I like to place my initialization in a single place so that I can control the order of initialization. But servlets make this difficult since the servlet container does not guarantee any order of initialization for your code. This means that each servlet is a potential source of initialization. Which again forces you to the singleton and lazy initialization model.
In Java you have single inheritance hierarchy, (which is a good thing) but Servlets take that away from you by forcing you to inherit from their class in essence giving you no inheritance choice at all. Inheritance should be used when we need to take advantage of polymorphism, but there is no polymorphism going on within servlets. What passible purpose does inheriting from HttpServlet serve which could not be addressed with an interface. Code reuse through inheritance is quite possibly the worst offense in software engineering. Composition over inheritance should always be the goal. The worse thing about this is it sets a precedence which is followed on most projects. Most web apps have deep inheritance hierarchies with Servlet at the top which seem to serve no other purpose than code reuse. Testing collaborators is easy since I can test each collaborator in isolation, but if collaboration is replaced with inheritance now my tests have to test the full inheritance hierarchy at once. Inheritance is all or nothing proposition.
OK, if you managed to survive the constructor, initialization, and inheritance, there are other subtle surprises waiting for you. We are building web apps, therefor the most common object lifetime should be equal to the duration of HTTP request. Instead we have servlets whose lifetime is lifetime of the JVM. This creates a huge problem since all of the HTTP request information/data now has to travel through the stack rather than being a part of the object. In other words we have static code-base and we push the data around through the method arguments. Sounds like procedural code to me. I don’t want to get into debate over OO vs Procedural, but I do want to point out that Java is OO, use it in that way. If you want to write rocedural code, use a procedural language. Play to the strengths of the language no to its weakness.
Surely there is nothing else which servlets do wrong. Ohh, but there is, let me introduce you to the Service Locators anty-pattern. The HttpServletRequest and HttpServletResponse are two huge service locators. They themselves hove nothing of interest, but they do have getter methods which have things which I need. Well actually, I don’t need that either. What I want is a User for example. What I have is request. Let’s see does this look familiar?
String cookie = request.getCookie(); long userId = cookieParser.parse(cookie).getUserId(); User user = userRepository.find(userId);
What I want is user but what I got is worthy of a Sherlock-Holms detective story as I try to piece together the identity of the user. If I had to do this in one location, I would not complain so much but I have to do this in every single servlet. Please, just give me the user, or whatever else which I need to get my work done. The thing is that request has cookie, but it is not the cookie I want, it is the user id which is embedded in the cookie. But it is not the user id, but the user i really want. This Law of Demeter violation is the direct result of two things: (1) service locator nature of the request / response and (2) the wrong lifetime of the servlet.
Now imagine trying to test this thing. First I have to call new, great that is easy as I have no argument constructor, but how do I get all of my mock dependencies into the servelet as collaborators? My only hope is setting some global state. But, wait the servlet is also responsible for initialization which means it is loaded with new operators, and those suck from the testability point of view. (see here) Now I really want to test the home page but the home page servlet inherits from the AuthorizedServlet which talks to the database to authenticate. Great now my every test needs to mock out the authentication even if it is not testing it. So if I change my authentication all my test are broken. Now I really just want to create a new User to test the home page but I have to place my user into a Mock Repository and assign a mock id. I than have to create a mock cookie and place it into a mock http request all the while hoping that I will be able to give you a mock UserRepesitory through some global state. I wish I was making this up, but Servlets are untestable in their current form.
Let’s see if we can solve these problems. First forget servlets and create your own classes which have your own minimal inheritance with preference for composition with object lifetime which equals that of the HTTP request so that your collaborators can ask for objects such as User directly in the constructor. This way testing your code is easy. Now for the servlet part, Since our code is HTTP servlet, request and response free it is both easy to test and incompatible. Therefor the job of the servlet is to call the new operators to build the object graph of collaborators with the right lifetime and to delegate the execution to our code. In essence the servlet becomes one big factory and object look up. It is still untestable as ever, but at least there is no logic to test there. Since the nature of the factories is that they either work or they blow up spectacularly.