The Source for Java Technology Collaboration
User: Password:



Simon Brown

Simon Brown's Blog

Testing MVC actions, mock objects and code coverage

Posted by simongbrown on January 21, 2004 at 08:41 AM | Comments (18)

Mock objects are the subject of several blogs again this week and they reminded me of a question that several people have asked me. In a web application, how do you unit test an MVC action?

In a previous blog entry, I highlighted the differences between implementations of the Servlet specification when it comes down to security and presented a fairly simple workaround. Subsequently, I now have a class called LoginAction that looks something like this.

import ...;

public class LoginAction extends Action {

  /**
   * Peforms the processing associated with this action.
   *
   * @param request  	The HttpServletRequest instance.
   * @param response   	The HttpServletResponse instance.
   * @return       	The name of the next view
   */
  public String process(HttpServletRequest request,
                        HttpServletResponse response)
      throws ServletException {

    if (request.getUserPrincipal() != null) {
      // some J2EE web containers don't allow programmatic access to the
      // principal information from resources that don't fall under a
      // security constraint - for this reason this information is placed into
      // the user's session
      AuthenticatedUser user = new AuthenticatedUser();
      user.setName(request.getUserPrincipal().getName());
      user.setBlogOwner(request.isUserInRole(Constants.BLOG_OWNER_ROLE));
      user.setBlogContributor(request.isUserInRole(Constants.BLOG_CONTRIBUTOR_ROLE));
      request.getSession().setAttribute(Constants.AUTHENTICATED_USER, user);
    }

    try {
      response.sendRedirect(someUrl);
    } catch (IOException ioe) {
      throw new ServletException(ioe);
    }

    return null;
  }

So then, how do you test this? Well, the solution that I've adopted is to use mock objects. The reasons? I want to be able to run my unit tests quickly and without the hassle of setting up a web/application server environment and deploying my code into it. A mock object is effectively a stub for some code that is typically implemented elsewhere. In this example, I am choosing to mock out the interfaces provided by the Servlet specification and this entails providing dummy implementations for those interfaces. As with everything, there is some work involved in building these mocks, but thankfully there are several prebuilt mock object implementations available including MockObjects, EasyMock and JMock.

Back to the question in hand, with mock objects, testing the action class becomes fairly straightforward.

  public void testAuthenticatedBlogOwner() {
    try {
      MockPrincipal principal = new MockPrincipal("simon");
      principal.addRole(Constants.BLOG_OWNER_ROLE);
      request.setUserPrincipal(principal);
      String result = action.process(request, response);
      assertNull(result);
      assertEquals(someUrl, response.getSendRedirect());

      // and also check that an AuthenticatedUser object has been created
      AuthenticatedUser user = (AuthenticatedUser)request.getSession().getAttribute(
        Constants.AUTHENTICATED_USER);
      assertEquals("simon", user.getName());
      assertTrue(user.isBlogOwner());
      assertFalse(user.isBlogContributor());
    } catch (ServletException e) {
      fail();
    }
  }

Like crazybob, I've also taken the approach of generating mock objects using an IDE. With IntelliJ it's straightforward - just create a new class, state that it implements an interface and choose "Implement methods..." from the Code menu. You might ask why I'm not using a prebuilt implementation and the answer is that I just don't need the sort of flexibility that they offer. The code I'm testing only uses a very tiny percentage of the Servlet API and I can easily generate my own simple mocks very quickly using a YAGNI approach. The existing mock object implementations have support for automatic verification, method invocation counts and so on. There's some really clever stuff in there, but I just don't need it ... yet. ;-)

Mock objects present an easy way of testing this MVC action and provide a way to test that action outside of the container. MVC actions are often one of the hardest parts of a web application to test, and using mock objects makes testing easier which in turn helps ensure confidence in your code. Saying this though, one thing still eludes me.

Clover running in IntelliJ IDEAIn an ideal world, everybody would aim for 100% unit test code coverage all of the time. In reality I don't think that this is possible. If you look back to the action code above, there is one part that typically won't be executed (see image).

Here, an IOException is thrown if there is a problem in redirecting the response. For example, part of the response could have already been committed. In the normal course of testing this action, that line of code will never get called. Of course, since I'm using mocks, I could write some code to cause that method to throw the relevant exception and acheive the full 100% code coverage but I'm not convinced that this necessarily provides any additional benefit or confidence that this piece of code will work as expected. This is just one example but what do you think? Would you put extra effort into testing this line of code to acheive 100% test coverage? If not, when do you know when to stop. I guess that at the end of the day, it's all about confidence and what works for you. I'd be interested to hear your thoughts on this.


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Do you floss every day?
    Do you floss every day?

    Posted by: johnm on January 21, 2004 at 11:23 AM

  • Yes
    Though I abstract out the Servlet spec stuff so that Struts Actions look like WebWork actions. All two of the actions receiving Http* are tested in most of my apps, and all the real actions which those invoke are tested as well.

    Posted by: brianm on January 21, 2004 at 11:57 AM

  • Does this make sense?
    I don't think this test makes much sense as a unit test. Doesn't it concern you that the amount of testing code is almost as much as the class being tested?

    And what exactly is being tested here? It seems like all the LoginAction does is put an AuthenticatedUser in the Session when there's a Principal in the request. Why does this warrant its own test? This seems like a pure implementation detail. What if the application changes later to use digest auth and no sessions? This may seem unlikely, but I hope you see the point. You're testing the implementation of a class. When the implementation changes your tests will break. Instead of testing implementation you might consider testing the contract. For example, you might have an AuthenticationManager interface with a SessionAuthenticationManager that pulled the AuthenticatedUser from the HttpSession. Then it might make sense to test this implementation/interface. But as things are now, this code seems very pointless to me.

    Posted by: ocean on January 21, 2004 at 12:13 PM

  • Does this make sense?
    For me, yes, this test makes sense. It's code that could go wrong and in fact I could have coded it wrong. Since this code is important to ensuring that the security of the webapp works, it is certainly something that I'd like to test. Do you not think it warrants it's own test?

    Yes, there is a lot of code but then you generally do seem to write more testing code. For example, take a method that adds two numbers together. Depending on how thorough you want to be, you'd probably want to test the various combinations of negative and positive numbers, along with any boundary conditions that might apply. It's a case of choosing whether you are doing white box or black box testing.

    I'm not sure I understand what you are getting at by saying that I'm testing the implementation details, and why this is a bad thing in this case. Yes, I am testing that an AuthenticatedUser is created when a Principal is in the request because this is exactly the functionality that this unit provides. If the code changes then I will have to change the tests, regardless of whether I wrap it up in a SessionAuthenticationManager class. From a white box perspective, I can write tests to cover each and every scenario through the code to ensure that I have a decent amount of test coverage. However, in saying that, I am also testing the contract - it just happens that my unit has some pre-conditions that must be satisfied before calling the process() method (the contract) and some post-conditions that need to be asserted afterwards. In order to test this unit in a standalone manner, I'm just providing mock implementations of the pre-conditions that will be satisfied by the Servlet container. The problem with testing infrastructure related code is that you can't get away from the implementation details. This is just one of those cases.

    I have some other thoughts that are kind of related to this, but I'll put them in another blog entry.

    Posted by: simongbrown on January 21, 2004 at 01:14 PM

  • Do you floss every day?
    So assuming that you do floss everyday, how many times do you floss everyday? Clearly more is better, but how much is too much?

    Posted by: simongbrown on January 21, 2004 at 01:22 PM

  • Do you floss every day?
    Rather than just give you a pat answer, let me very humbly suggest that you go do some research about flossing and dental hygiene. It's all there.

    Posted by: johnm on January 21, 2004 at 03:17 PM

  • Does this make sense?
    Simon,

    What I was basically trying to get across was that you should test interfaces not implementations. I don't think your test adds much value because it's just describing what code does not necessarily what the code is supposed to do. This is a subtle but important difference. Not only that, the test itself is very brittle and will have to change every time the actual code changes. This is clearly not good. Personally I don't see much value in testing mini-controller Action classes (either Struts or Swing) precisely because they are so volatile and tend to change.

    Posted by: ocean on January 22, 2004 at 02:27 AM

  • Does this make sense?
    Surely they reason that this code does change frequently is a very good reason to write tests for it?

    Actions make up the bulk of the logic in an MVC type application, therefore but not testing them you are risking not identifying a lot of potential problems in your system.....

    I really don't see how too much testing (which seems to be what you are saying) can ever be a bad thing......

    Posted by: sam_dalton on January 22, 2004 at 02:44 AM

  • Does this make sense?
    Agreed, if you can write tests that aren't brittle then that's certainly a good thing. At some point in time though, you have to test the implementation and what the code is doing.

    I think your last point is very interesting and I guess it comes back to confidence again. I do think it's worth testing the actions, even if they are simple adapters to business logic encapsulated elsewhere.

    Posted by: simongbrown on January 22, 2004 at 03:00 AM

  • Do you floss every day?
    (I can't resist responding, even though this is off topic.)

    A dental student I knew told me that flossing once a day was fine. The important thing is to get in there and break up the bacteria before it hardens the crud (my term) into tartar.

    Posted by: jt2190 on January 22, 2004 at 07:53 AM

  • Yes, too much testing can be a bad thing
    There are two points I'd like to bring up:

    (1) As Glen Vanderberg pointed out in Naked Objects Everywhere, writing unit tests is just as much about the design of code as it is about testing the code. Well designed code is testable.

    (2) I have seen programmers (myself included) become confused by the question of what to test. You should test that your code upholds its API contract, and no more. Don't test things provided by other code.

    Posted by: jt2190 on January 22, 2004 at 08:27 AM

  • Yes, too much testing can be a bad thing
    The full text of jt2190's response was:

    There are two points I'd like to bring up:

    (1) As Glen Vanderberg pointed out in Naked Objects Everywhere,
    writing unit tests is just as much about the design of code as it is about
    testing the code. Well designed code is testable.

    (2) I have seen programmers (myself included) become confused by the
    question of what to test. You should test that your code upholds its API
    contract, and no more. Don't test things provided by other code.

    Posted by: simongbrown on January 22, 2004 at 08:44 AM

  • How much testing is a bad thing?
    It's interesting to see how TDD has changed the perception of unit testing and although I've not TDD'd this particular class, I still think that it's testable. Also, I do think that I'm testing the contract that this class provides.

    This brings me back to my initial question, how much testing is too much? Can it be measured by code coverage tools or is it more of a warm fuzzy feeling thing?

    Posted by: simongbrown on January 22, 2004 at 08:48 AM

  • Does this make sense?
    "Actions make up the bulk of the logic in an MVC type application"

    This isn't necessarily always the case. Many argue that the action class should delegate the logic to an underlying layer containing all the business logic. Many do choose to include quite a bit of logic in the action class, may do not, myself included. That said, I still think there is value in testing the action classes.

    Posted by: psummers8 on January 22, 2004 at 09:02 AM

  • When 100% is too much
    The law of diminishing returns applies to code coverage; you can reach a point at which it becomes unproductive investing the time and effort required to construct harnesses or mocks to simulate error cases or exceptions that are unlikely or never going to happen in the real world, or can't be expected to be handled reasonably by the application.

    To determine a good coverage goal, you can go back to requirements and end-user expectations, and decide what proportion of effort you want to devote to obtaining coverage. Clearly if your application is mission- or safety-critical, a high level of coverage is desired, and more effort is warranted.

    At Cortex, we aim for coverage on all branches in an application, and all exceptional cases that the application is supposed to cope with or can reasonably expect to see in it's life. This usually works out at above 80% coverage, depending largely on the nature of the application.

    Incidently, the screenshot in your article shows the Clover plugin for IntelliJ IDEA. With this plugin it's possible to set a context filter when viewing coverage, so that certain blocks are excluded from the coverage statistics. In the case above, you can get a "feel" for the coverage of non-exceptional code by filtering out 'catch' blocks. See the userguide for more details.

    Cheers,
    -Brendan
    http://www.thecortex.net/clover

    Posted by: brendan on January 22, 2004 at 08:54 PM

  • You don't have to roll all your own mocks
    If you roll your own, it's a chore implementing decent failure messages and you end up with lots of duplicated checking code. That's why we refactored out the Expectation classes. They'll probably make things easier and more consistent.

    http://stevef.truemesh.com/archives/000169.html

    Posted by: stevef on January 23, 2004 at 07:41 AM

  • wow power leveling
    wow powerleveling
    wow power leveling
    wow gold
    wow items
    feelingame.com
    wow tips
    Most Valuable WOW Power Leveling Service
    wow power leveling faq
    cheap wow power leveling
    wow power leveling
    wow powerleveling
    wow power lvl

    Posted by: wowleveling3 on December 14, 2007 at 12:06 AM

  • 网络营销软件
    网络营销软件
    网络营销软件
    群发软件
    群发软件
    ---
    群发软件
    网络营销软件
    论坛群发软件
    网站排名软件
    群发软件
    推广小助手破解版
    论坛群发软件
    网站排名软件
    群发软件
    推荐给你很好的群发软件和信息群发软件和供求群发软件
    推荐给你很好的群发软件和信息群发软件和供求群发软件博客群发软件网络营销软件网络营销软件
    网站排名软件网站排名软件网站优化软件信息群发软件信息群发软件信息群发软件论坛群发软件网站推广软件网站推广软件博客群发软件博客群发软件
    群发软件
    网络营销软件
    网站推广软件
    群发软件群发软件博客群发软件论坛群发软件网络营销软件论坛群发软件
    信息群发软件推广软件网站推广软件网络营销软件网站推广软件群发软件网站排名软件网站推广软件博客群发软件论坛群发软件群发软件网站排名软件网站推广软件博客群发软件论坛群发软件
    网站排名软件
    博客群发软件
    网站排名软件
    网站推广软件
    群发软件信息群发软件
    免费论坛群发软件
    论坛群发软件
    网站排名软件
    免费博客群发软件
    网站推广软件

    群发软件
    博客群发软件
    网站排名软件
    网站推广软件
    群发软件信息群发软件
    免费论坛群发软件
    论坛群发软件
    网站排名软件
    免费博客群发软件
    博客群发软件
    信息群发软件
    论坛群发软件
    信息群发软件
    博客群发软件
    qq群发软件
    邮件群发软件
    博客群建软件
    企业名录搜索软件
    信息群发软件
    邮件群发软件
    论坛群发软件
    博客群发软件
    网站推广软件
    网络营销软件
    全能营销破解版
    网络营销软件
    论坛群发软件
    论坛群发软件
    论坛群发软件
    网络营销软件
    信息群发软件
    信息群发软件
    信息群发软件
    群发软件
    论坛群发软件

    Posted by: sun98989 on December 30, 2007 at 05:11 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds