Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between a Seam and a Mock?

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?
like image 452
javing Avatar asked Mar 10 '13 21:03

javing


People also ask

What are seams in testing?

Test seams are a simple way of replacing or expanding source code in production parts of a program for test purposes. If, for example, the behavior of certain statements prevents tests from running, the unit test can replace them with suitable alternatives.

What are seams in code?

A seam is a place where you can alter behavior in your program without editing in that place. What is Seam in Software? The analogy is a seam in clothing: A line where two piece of fabrics are stitched together. A seam in software as a place where two parts of the code meet and where something else can be injected.

What is a component testing seam?

Seams are a technique to help us test hard-to-test code. They allow us to replace production functionality with different functionality while testing. We typically do this by replacing a real dependency with a test double or mock.


2 Answers

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.

like image 156
tallseth Avatar answered Sep 21 '22 08:09

tallseth


Seams

A seam is a place that allows you to modify the behavior without modifying the code.

In your example, the following is an example of an Object seam (if i'm not mistaken). It allows you to pass in a different object without having to change the code. hence it is a type of seam.

public void doSomeProcessing(ParameterSource request) {..}

By making the parameter an abstract type (instead of a concrete class), you have introduced a seam. The seam now allows you to modify the behavior of the method without editing its code - i.e. at the place of invokation, I can pass in a different object and make the method do something else.

Mocks

Now instead of creating your custom fake (creating a subtype of the interface), you could using a Mock framework to do something like this

Mocks also support asserting whether specific methods were called on it, argument matching and other nifty functionality to be consumed by tests. Less test code to maintain. Mocks are primarily used to assert that a specific call is being made to a dependency. In your example, you seem to be in need of a Stub, you just want to return a canned value.

Pardon my rusty JMock..

 @Test
    public void
    the_output_does_not_get_populated_when_the_request_is_empty
    () {
        Mockery context = new Mockery();
        final ParameterSource mockSource = context.mock(ParameterSource.class)

context.checking(new Expectations(){{
    oneOf(mockSource).getParameters(); 
            will(returnValue(new string[]{"ParamA","ParamB","ParamC"} );
}});
        aBitOfLegacy.populate(mockSource);
        assertThat(aBitOfLegacy.output,not(EMPTY));
    }

in .Net

var mockSource = new Mock<ParameterSource>();
mockSource.Setup(src => src.GetParameters())
          .Returns(new []{"ParamA","ParamB","ParamC"});
like image 29
Gishu Avatar answered Sep 21 '22 08:09

Gishu