What is the difference between a Seam and a Mock?

javing picture javing · Mar 10, 2013 · Viewed 7.1k times · Source

Its being a few months since I am working with java legacy code, this are some of the things I am dealing with:

  • 0% test coverage.
  • Huge functions in occasions I even saw some with more than 300 lines of code.
  • Lots of private methods and in occasions static methods.
  • Highly tight coupled code.

At the beginning I was very confused, I found difficult to use TDD in the legacy. After doing katas for weeks and practicing my unit testing and mocking skills, my fear has decreased and I feel a bit more confident. Recently I discovered a book called: working effectivelly with legacy, I didn't read it, I just had a look at the table of contents and I discovered something that is new for me, The Seams. Apparently this is very important when working in the legacy.

I think that this Seams could help me alot in breaking dependencies and make my code testeable so I can increase the code coverage and make my unit testing more precise.

But I have a lot of doubts:

  • Can somebody explain me the difference between a seam and a mock?
  • Do Seams, break TDD rules in what regards not touching production code, before is tested?
  • Could you show me some simple example that compares a Seam and a Mock?

Below I would like to paste an example I did today where I tried to break a dependency with the goal of making the code testeable and finally increasing test coverage. I would appreciate if you could comment a bit if you see some mistakes?

This is how the legacy code looked like at the beginning:

public class ABitOfLegacy
{
    private String sampleTitle;
    String output; 

    public void doSomeProcessing(HttpServletRequest request) {
    String [] values = request.getParameterValues(sampleTitle);
        if (values != null && values.length > 0)
        {
            output = sampleTitle + new Date().toString() + values[0];
        }

    }   
}

If I just add a unit test that calls that method and asserts that variable output, has a certain value after the call,then I would be making a mistake, because I am not unit testing, I would be doing integration testing. So what I need to do, Is get rid of the dependency I have in the parameter. To do So, I replace the parameter with an interface:

public class ABitOfLegacy
{
    private String sampleTitle;
    String output; 

    public void doSomeProcessing(ParameterSource request) {
    String [] values = request.getParameters(sampleTitle);
        if (values != null && values.length > 0)
        {
            output = sampleTitle + new Date().toString() + values[0];
        }
    }

}

This is how the interface looks like:

public interface ParameterSource {
    String[] getParameters(String name);
}

The next thing I do, is create my own implementation of that interface but I include the HttpServletRequest as a global variable and I implement the method of the interface using the method/s of HttpServletRequest:

public class HttpServletRequestParameterSource implements ParameterSource {

    private HttpServletRequest request;

    public HttpServletRequestParameterSource(HttpServletRequest request) {
        this.request = request;
    }

    public String[] getParameters(String name) {
        return request.getParameterValues(name);
    }

}

Until this point, I think that all the modifications on the production code were safe. Now I create the Seam in my test package. If I understood well, now I am able to safely change the behavoir of the Seam. This is how I do it:

public class FakeParameterSource implements ParameterSource {

    public String[] values = {"ParamA","ParamB","ParamC"};

    public String[] getParameters(String name) {
        return values;
    }
}

And the final step, would be to get support from the Seam, to test the original behavoir of the method.

import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import code.ABitOfLegacyRefactored;
import static org.hamcrest.Matchers.*;

public class ABitOfLegacySpecification {

    private ABitOfLegacy aBitOfLegacy;
    private String EMPTY = null;

    @Before
    public void initialize() {
        aBitOfLegacy = new ABitOfLegacy();
    }

    @Test
    public void
    the_output_gets_populated_when_the_request_is_not_empty
    () {
        FakeParameterSource fakeParameterSource = new FakeParameterSource();
        aBitOfLegacy.doSomeProcessing(fakeParameterSource);
        assertThat(aBitOfLegacy.output,not(EMPTY));
    }

    @Test(expected=NullPointerException.class)
    public void
    should_throw_an_exception_if_the_request_is_null
    () {
        aBitOfLegacy.doSomeProcessing(null);
    }   
}

This will give me 100% test coverage. I appreciate your thoughts:

  • Did I break the dependency correctly?
  • Are the unit tests missing something?
  • What could be done better?
  • Is this example good enough to help me understand the difference between a Seam and a Mock?
  • How could a mock help me here if I don't use the Seam?

Answer

tallseth picture tallseth · Mar 11, 2013

A seam is a place in the code that you can insert a modification in behavior. You created a seam when you setup injection of your dependency.

One way to take advantage of a seam is to insert some sort of fake. Fake's can be hand-rolled, as in your example, or be created with a tool, like Mockito.

So, a mock is a type of fake, and a fake is often used by taking advantage of a Seam.

As for your tests and the way you broke the dependency, that's pretty much how I would have done it.