Capturing method parameter in jMock to pass to a stubbed implementation
Asked Answered
K

3

13

I wish to achieve the following behavior. My class under test has a dependency on some other class, I wish to mock this dependency with jMock. Most of the methods would return some standard values, but there is one method, where I wish to make a call to a stubbed implementation, I know I can call this method from the will(...) but I want the method to be called by the exact same parameters that were passed to the mocked method.

Test

@Test
public void MyTest(){
    Mockery context = new Mockery() {
        {
            setImposteriser(ClassImposteriser.INSTANCE);
        }
    };
    IDependency mockObject = context.mock(IDependency.class);
    Expectations exp = new Expectations() {         
        {
            allowing(mockObject).methodToInvoke(????);
            will(stubMethodToBeInvokedInstead(????));
        }       
    };      
}

Interface

public interface IDependency {
    public int methodToInvoke(int arg);
}

Method to be called instead

public int stubMethodToBeInvokedInstead(int arg){
    return arg;
}

So how do I capture the parameter that were passed to the method being mocked, so I could pass them to the stubbed method instead?

EDIT

Just to give another example, let's say I wish to mock the INameSource dependency in the following (C#) code, to test the class Speaker

public class Speaker
{
  private readonly string firstName;
  private readonly string surname;
  private INameSource nameSource ;
 public Speaker(string firstName, string surname, INameSource nameSource)
  {
    this.firstName = firstName;
    this.surname = surname;
    this.nameSource = nameSource;
  }
  public string Introduce()
  {
    string name = nameSource.CreateName(firstName, surname);
    return string.Format("Hi, my name is {0}", name);
  }
}
public interface INameSource
{
  string CreateName(string firstName, string surname);
}

This is how it can be done in Rhino Mocks for C# I understand it can't be as easy as this since delegates are missing in Java

Kutuzov answered 31/8, 2012 at 7:50 Comment(0)
S
22

The solution from Duncan works well, but there is even a simpler solution without resort to a custom matcher. Just use the Invocation argument that is passed to the CustomActions invoke method. At this argument you can call the getParameter(long i) method that gives you the value from the call.

So instead of this

return matcher.getLastValue();

use this

return (Integer) invocation.getParameter(0);

Now you don't need the StoringMatcher anymore: Duncans example looks now like this

@RunWith(JMock.class)
public class Example {

  private Mockery context = new JUnit4Mockery();

  @Test
  public void Test() {

    final IDependency mockObject = context.mock(IDependency.class);

    context.checking(new Expectations() {
      {
        // No custom matcher required here
        allowing(mockObject).methodToInvoke(with(any(Integer.class)));

        // The action will return the first argument of the method invocation.
        will(new CustomAction("returns first arg") {
          @Override
          public Object invoke(Invocation invocation) throws Throwable {
            return (Integer) invocation.getParameter(0);
          }
        });
      }
    });

    Integer test1 = 1;
    Integer test2 = 1;

    // Confirm the object passed to the mocked method is returned
    Assert.assertEquals((Object) test1, mockObject.methodToInvoke(test1));
    Assert.assertEquals((Object) test2, mockObject.methodToInvoke(test2));
  }

  public interface IDependency {
    public int methodToInvoke(int arg);
  }
Soubise answered 12/11, 2012 at 19:52 Comment(3)
cool something very similar to what I did in C#. I can now simply write return stubMethodToBeInvokedInstead((Integer) invocation.getParameter(0)); without any custom matchers or anything, just an anonymous inline custom action???!!!... very close to lambdas in this case.. Will report back after giving it a try... thanksKutuzov
Yes! this is it. I managed to use this strategy to partially override the default returned value. I was using the method(r) to mock similar methods with different return types (boolean and void), but wanted the default boolean to be true... I should probably write a full post about it, if I could find the time.Deletion
Nice solution, much tidier!Juniejunieta
J
1

Like Augusto, I'm not convinced this is a good idea in general. However, I couldn't resist having a little play. I created a custom matcher and a custom action which store and return the argument supplied.

Note: this is far from production-ready code; I just had some fun. Here's a self-contained unit test which proves the solution:

@RunWith(JMock.class)
public class Example {

  private Mockery context = new JUnit4Mockery();

  @Test
  public void Test() {

    final StoringMatcher matcher = new StoringMatcher();
    final IDependency mockObject = context.mock(IDependency.class);

    context.checking(new Expectations() {
      {
        // The matcher will accept any Integer and store it
        allowing(mockObject).methodToInvoke(with(matcher));

        // The action will pop the last object used and return it.
        will(new CustomAction("returns previous arg") {
          @Override
          public Object invoke(Invocation invocation) throws Throwable {
            return matcher.getLastValue();
          }
        });
      }
    });

    Integer test1 = 1;
    Integer test2 = 1;

    // Confirm the object passed to the mocked method is returned
    Assert.assertEquals((Object) test1, mockObject.methodToInvoke(test1));
    Assert.assertEquals((Object) test2, mockObject.methodToInvoke(test2));
  }

  public interface IDependency {
    public int methodToInvoke(int arg);
  }

  private static class StoringMatcher extends BaseMatcher<Integer> {

    private final List<Integer> objects = new ArrayList<Integer>();

    @Override
    public boolean matches(Object item) {
      if (item instanceof Integer) {
        objects.add((Integer) item);
        return true;
      }

      return false;
    }

    @Override
    public void describeTo(Description description) {
      description.appendText("any integer");
    }

    public Integer getLastValue() {
      return objects.remove(0);
    }
  }
}

A Better Plan

Now that you've provided a concrete example, I can show you how to test this in Java without resorting to my JMock hackery above.

Firstly, some Java versions of what you posted:

public class Speaker {
  private final String firstName;
  private final String surname;
  private final NameSource nameSource;

  public Speaker(String firstName, String surname, NameSource nameSource) {
    this.firstName = firstName;
    this.surname = surname;
    this.nameSource = nameSource;
  }

  public String introduce() {
    String name = nameSource.createName(firstName, surname);
    return String.format("Hi, my name is %s", name);
  }
}

public interface NameSource {
  String createName(String firstName, String surname);
}

public class Formal implements NameSource {
  @Override
  public String createName(String firstName, String surname) {
    return String.format("%s %s", firstName, surname);
  }    
}

Then, a test which exercises all the useful features of the classes, without resorting to what you were originally asking for.

@RunWith(JMock.class)
public class ExampleTest {

  private Mockery context = new JUnit4Mockery();

  @Test
  public void testFormalName() {
    // I would separately test implementations of NameSource
    Assert.assertEquals("Joe Bloggs", new Formal().createName("Joe", "Bloggs"));
  }

  @Test
  public void testSpeaker() {
    // I would then test only the important features of Speaker, namely
    // that it passes the right values to the NameSource and uses the
    // response correctly
    final NameSource nameSource = context.mock(NameSource.class);
    final String firstName = "Foo";
    final String lastName = "Bar";
    final String response = "Blah";

    context.checking(new Expectations() {
      {
        // We expect one invocation with the correct params
        oneOf(nameSource).createName(firstName, lastName);
        // We don't care what it returns, we just need to know it
        will(returnValue(response));
      }
    });

    Assert.assertEquals(String.format("Hi, my name is %s", response),
        new Speaker(firstName, lastName, nameSource).introduce());
  }
}
Juniejunieta answered 4/9, 2012 at 13:45 Comment(4)
Nice... I think I can generalize this... Just moved from C# to java recently, didn't know about custom matchers and actions. I have updated the question to show how it is supported in Rhino Mocks for C# and links that gives its usageKutuzov
@JugalThakkar I assume you get notified about changes to answers, but I thought I'd better ping you anyway. See above for a way to solve your problem without resorting to anything that we would consider strange.Juniejunieta
Thanks for the updated answer, I see the mistake that I was committing, may be I was trying to (or at least ending up) testing two things at once. Can't believe how I missed it. My real use case is a little more complex then the example, but now I think I am on the right path. Anyway the main purpose was to find an alternative to the Rhino Mock's Do(...) action that let you pass alternate delegate for a given call of a given method of a mock object. The storing matcher helped me understand better the way jMock worksKutuzov
@JugalThakkar Fine, but bear in mind it is not normal to do what I suggested at the start of my answer. I'm sure there is always an alternative. Feel free to ask another question in the future if you find yourself fiddling JMock too much.Juniejunieta
K
0

JMock doesn't support your use case (or any other mocking framework I know of in java).

There's a little voice in my head that says that what you're trying to do is not ideal and that your unit test might be to complicated (maybe it's testing too much code/logic?). One of the problems I see, is that you don't know which values those mocks need to return and you're plugging something else, which might make each run irreproducible.

Kcal answered 31/8, 2012 at 9:46 Comment(5)
I don't think I am testing too much, lets say this method is just multiplying the passed number with a factor. This factor is internal to the dependency (may be it reads from a config file). So in order to avoid reading the config file (or whatever it does that it does), I would like to mock the method out by lets say returning always returning 3 times the number passed. I know this may not be the best example. I have done similar tests in C# with rhino mocks.Kutuzov
Your example has a design flaw: reading parameters from a config file and calculating a value are 2 different things that should be done by different classes. I don't know how familiar you're with dependency inversion, but I would suggest you to read a bit about that.Kcal
yes i am aware about these techniques.That's how I am injecting the dependency I want to mockKutuzov
Thanks for pointing out the design flaw. But it is just an example, I l c if I can add my real use case hereKutuzov
you can check the edited question for a possibly valid use case and how Rhino Mock framework in C# supports thatKutuzov

© 2022 - 2024 — McMap. All rights reserved.