Chariot Training Classes

Training Courses

I run Chariot's training and mentoring services. We provide training in AngularJS, HTML5, Spring, Hibernate, Maven, Scala, and more.

Chariot Education Services

Technology

Chariot Emerging Tech

Learn about upcoming technologies and trends from my colleagues at Chariot Solutions.

Resources

Chariot Conferences

Podcasts

Entries in services-and-repositories (1)

Friday
Feb242012

Quizzo Friday - Goin' all layered on the darn thing

I'm currently working on a somewhat complex, ajax-based web flow. See commit 505fbe7989f62003a4188a4baab5b85cc31077fb for the additional methods and constructs. It's not working yet, so I figured I'd be a good little boy and write tests.

Now, I could be Mr. Smarty-pants and work with the models directly, and I've been doing so. However, here's where that breaks down - when I want to do some more involved application development, and want to try some web-flow tests using the WebFlow mock system, and need to mock the entity layer. That's just not so easy.

The challenge

Ok, take a look at what I'm up against here (click the image to see the fragment of the webflow, courtesy of IntelliJ's great WebFlow editor).
I have a section of the web flow that registers a new user. Two steps are currently involved, one with the team name, the second with a list of members (and this one is not yet working so don't bother reviewing it...)

I'll want to test the method being guarded by the exit, which is defined this way in the web flow:

<transition on="continue" to="ready-to-play">
  <evaluate expression="quizzoFlowManager.saveTeamData(flowRequestContext)"/>
</transition>

The method in ~.web.flow.QuizzoFlowManagerBean looks like this currently:

  @Override
  public Event saveTeamData(RequestContext flowRequestContext) throws FlowException {
    TeamSetupForm teamSetupForm = 
       (TeamSetupForm)flowRequestContext.getViewScope()
       .get("teamSetupForm");

    Team team = new Team();
    team.setName(teamSetupForm.getName());
    team.setMission(teamSetupForm.getMessage());

    Iterator<String> memberNamesIterator = 
      teamSetupForm.getTeamMemberNames().iterator();

    while (memberNamesIterator.hasNext()) {
      String teamMemberName = memberNamesIterator.next();
      TeamMember member = new TeamMember();
      member.setName(teamMemberName);
      team.getTeamMembers().add(member);
    }

    team.setQuizRun(stateMachine.getQuizRun());
    team.persist();
    flowRequestContext.getFlowScope().put("team", team);
    return success();
  }

This is web-flow cruft. It's kind of like controller code, and it's defined in a Spring Bean that lives as a web-layer module. There are several problems with this:

  • It's doing persistence in a web-tier object
  • I don't want to make a web-tier object @Transactional
  • If I'm writing tests, how do I mock the entity layer without a lot of headache?

In thinking of how to express a test against this logic, I'm struck with how complex the test needs to be. It's tough enough to set up the Web Flow layer with the appropriate testing. But to then have to do some magic (see this post on @MockStaticEntityMethods - it hurts us, Baggins!).

I consider a good test to be documentation of the use-case or exercising of the code in question. That's what it should be. I'm sensing coupling here to both the web layer AND the data layer. Not so good, in my feeling. Although it's nice to have model objects and throw around Active Record calls everywhere for prototyping, I think this is where it falls down, in testing without being able to mock a service or repository layer cleanly.

Going all Repository on it

Ok, so how do I set up a repository for each bean? Easy-peasy.

repository jpa --entity ~.model.Answer        --interface ~.db.AnswerRepository
repository jpa --entity ~.model.Choice        --interface ~.db.ChoiceRepository
repository jpa --entity ~.model.Question      --interface ~.db.QuestionRepository
repository jpa --entity ~.model.Quiz          --interface ~.db.QuizRepository
repository jpa --entity ~.model.QuizRun       --interface ~.db.QuizRunRepository
repository jpa --entity ~.model.Team          --interface ~.db.TeamRepository
repository jpa --entity ~.model.TeamMember    --interface ~.db.TeamMemberRepository

Forgive me if my format looks a little odd - I used TextMate (my favorite Mac editor in the whole wide world) to manipulate the text using column pasting.

Now, I have a repository layer. I can do the same thing to add a service layer, but it needs a little more gusto (line breaks are for readability, it doesn't fit on one line in the blog):

service --entity ~.model.Answer --interface ~.service.AnswerService 
         --class ~.service.AnswerServiceBean
service --entity ~.model.Choice        --interface ~.service.ChoiceService
        --class ~.service.ChoiceServiceBean
service --entity ~.model.Question      --interface ~.service.QuestionService
      --class ~.service.QuestionServiceBean
service --entity ~.model.Quiz          --interface ~.service.QuizService
          --class ~.service.QuizServiceBean
service --entity ~.model.QuizRun       --interface ~.service.QuizRunService
       --class ~.service.QuizRunServiceBean
service --entity ~.model.Team          --interface ~.service.TeamService
          --class ~.service.TeamServiceBean
service --entity ~.model.TeamMember    --interface ~.service.TeamMemberService
    --class ~.service.TeamMemberServiceBean

Now, I have a true repository-and-service layer for my system. My controllers and integration tests automatically convert to using the services and repositories once they exist:

@RequestMapping(method = RequestMethod.POST, produces = "text/html")
public String TeamController.create(...) {
  if (bindingResult.hasErrors()) {
    populateEditForm(uiModel, team);
    return "admin/teams/create";
  }
  
  uiModel.asMap().clear();
  teamService.saveTeam(team);
  return "redirect:/admin/teams/" ...
}

It is special, isn't it. Next up, we'll show you how to unit test the logic in our web flow method.