Sunday, March 3, 2013

A Little (JUnit) Help

When you do one of those Code Katas, or have a green field project the tests are easy to write. Writing the tests as you code it makes it pretty easy to write the tests. In fact, if the tests start becoming hard to write you probably need to re-look at your code.

Unfortunately, we usually don't have this luxury, because we have the dreaded legacy code. So I've always looked for some way to help me get started on writing my unit tests. First, no I don't want something to write my tests for me, but it would be nice to be able to create some of the setup that makes it easier to get started.

So I was pretty excited when a developer on a project I was just on told me about JUnit Helper. It's an eclipse plugin that take some of the grunt work out of creating tests for legacy code. It has some options to change the mocking libraries and some of the structure of the tests. These are set by going to Windows->Preferences->JUnit Helper. The readme in the github directory has a pretty good explanation of the project so I won't go into that, instead I will show you an example of how it works.

This is a typically Spring MVC Controller with a service wired in

package org.daneking.profile.web.controller.person;

import org.daneking.profile.domain.person.Person;
import org.daneking.profile.service.PersonService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class TestController {

    private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);

    @Autowired
    private PersonService service;

    public void setService(final PersonService service) {
        this.service = service;
    }

    @RequestMapping(value = "/person/add/", method = RequestMethod.GET)
    public String loadAdd(final Model model) {
        LOGGER.info("Get Request to add");
        return add(model, new Person());
    }

    private String add(final Model model, final Person person) {
        model.addAttribute("person", person);
        return "add";
    }

    @RequestMapping(value = { "/person/add/{id}" }, method = RequestMethod.POST)
    public String save(@Validated final Person person) {
        LOGGER.info("Saving a person");
        final Person persistedEntity = service.save(person);
        // Verify persons was saved
        Assert.notNull(persistedEntity);
        LOGGER.info("Save Person");
        // Post Redirect Pattern to prevent double submits
        return "redirect:/person/list/";
    }

}
And here is the test it generated

package org.daneking.profile.web.controller.person;

import org.daneking.profile.web.controller.person.TestController.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import static org.mockito.BDDMockito.*;

import java.util.Iterator;
import org.daneking.profile.domain.person.Person;
import org.daneking.profile.service.PersonService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
public class TestControllerTest {

    @Test
    public void type() throws Exception {
        // TODO auto-generated by JUnit Helper.
        assertThat(TestController.class, notNullValue());
    }

    @Test
    public void instantiation() throws Exception {
        // TODO auto-generated by JUnit Helper.
        TestController target = new TestController();
        assertThat(target, notNullValue());
    }

    @Test
    public void loadEdit_Accepts_Long_Model() throws Exception {
        // TODO auto-generated by JUnit Helper.
        TestController target = new TestController();
        // given
        Long id = null;
        Model model = mock(Model.class);
        // e.g. : given(mocked.called()).willReturn(1);
        // when
        String actual = target.loadEdit(id, model);
        // then
        // e.g. : verify(mocked).called();
        String expected = null;
        assertThat(actual, is(equalTo(expected)));
    }

    @Test
    public void loadAdd_Accepts_Model() throws Exception {
        // TODO auto-generated by JUnit Helper.
        TestController target = new TestController();
        // given
        Model model = mock(Model.class);
        // e.g. : given(mocked.called()).willReturn(1);
        // when
        String actual = target.loadAdd(model);
        // then
        // e.g. : verify(mocked).called();
        String expected = null;
        assertThat(actual, is(equalTo(expected)));
    }

    @Test
    public void save_Accepts_Person() throws Exception {
        // TODO auto-generated by JUnit Helper.
        TestController target = new TestController();
        // given
        Person person = mock(Person.class);
        // e.g. : given(mocked.called()).willReturn(1);
        // when
        String actual = target.save(person);
        // then
        // e.g. : verify(mocked).called();
        String expected = null;
        assertThat(actual, is(equalTo(expected)));
    }

    @Test
    public void loadList_Accepts_Model() throws Exception {
        // TODO auto-generated by JUnit Helper.
        TestController target = new TestController();
        // given
        Model model = mock(Model.class);
        // e.g. : given(mocked.called()).willReturn(1);
        // when
        String actual = target.loadList(model);
        // then
        // e.g. : verify(mocked).called();
        String expected = null;
        assertThat(actual, is(equalTo(expected)));
    }

    @Test
    public void filteredList_Accepts_String_Model() throws Exception {
        // TODO auto-generated by JUnit Helper.
        TestController target = new TestController();
        // given
        String filter = null;
        Model model = mock(Model.class);
        // e.g. : given(mocked.called()).willReturn(1);
        // when
        String actual = target.filteredList(filter, model);
        // then
        // e.g. : verify(mocked).called();
        String expected = null;
        assertThat(actual, is(equalTo(expected)));
    }

    @Test
    public void deleteById_Accepts_Long_Model() throws Exception {
        // TODO auto-generated by JUnit Helper.
        TestController target = new TestController();
        // given
        Long id = null;
        Model model = mock(Model.class);
        // e.g. : given(mocked.called()).willReturn(1);
        // when
        String actual = target.deleteById(id, model);
        // then
        // e.g. : verify(mocked).called();
        String expected = null;
        assertThat(actual, is(equalTo(expected)));
    }

    @Test
    public void details_Accepts_Long_Model() throws Exception {
        // TODO auto-generated by JUnit Helper.
        TestController target = new TestController();
        // given
        Long id = null;
        Model model = mock(Model.class);
        // e.g. : given(mocked.called()).willReturn(1);
        // when
        String actual = target.details(id, model);
        // then
        // e.g. : verify(mocked).called();
        String expected = null;
        assertThat(actual, is(equalTo(expected)));
    }
   @Test
   public void setService_Accepts_PersonService() throws Exception {
 // TODO auto-generated by JUnit Helper.
        TestController target = new TestController();
 // given
 PersonService service = mock(PersonService.class);
 // e.g. : given(mocked.called()).willReturn(1);
 // when
 target.setService(service);
 // then
 // e.g. : verify(mocked).called();
   }

}
 
 OK, what did this give us and what's left to do. Well first these tests probably won't pass. For instance, the controller makes a call to save, but because the save method is not mocked it would fail. Also, to get the service to mock I had to change the default preferences to mock setter methods. I would also move the creation of the controller and the mock service to a setup. But it actually got a lot of my setup done I just have to move it around.

I'm still playing around the plugin and with some of the preference modifications. One thing I did was change the default Accepts, Throws and Returns and set my mocking preference. One gotcha I discovered is if you import a project into your workspace, for instance a github project, it will try to create the test in your workspace and not where the project is. This is mostly minor stuff though, and for getting some legacy code test cranked out it seems pretty useful.