12

我希望实现以下行为。我的测试类依赖于其他类,我希望用 jMock 模拟这种依赖。will(...)大多数方法会返回一些标准值,但是有一种方法,我希望调用存根实现,我知道我可以从传递给模拟方法。

测试

@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(????));
        }       
    };      
}

界面

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

要调用的方法

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

那么如何捕获传递给被模拟方法的参数,以便我可以将它们传递给存根方法呢?

编辑

再举一个例子,假设我希望INameSource在以下 (C#) 代码中模拟依赖项,以测试 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);
}

这就是如何在 Rhino Mocks for C# 中完成的我知道它不可能像这样简单,因为 Java 中缺少委托

4

3 回答 3

22

Duncan 的解决方案效果很好,但甚至还有一个更简单的解决方案,无需借助自定义匹配器。只需使用传递给 CustomActions 调用方法的 Invocation 参数。在此参数中,您可以调用 getParameter(long i) 方法,该方法为您提供调用中的值。

所以代替这个

return matcher.getLastValue();

用这个

return (Integer) invocation.getParameter(0);

现在您不再需要 StoringMatcher:Duncans 示例现在看起来像这样

@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);
  }
于 2012-11-12T19:52:29.737 回答
1

像奥古斯托一样,我不相信这是一个好主意。然而,我忍不住玩了一点。我创建了一个自定义匹配器和一个自定义操作,用于存储和返回提供的参数。

注意:这远非生产就绪代码;我只是玩得很​​开心。这是一个独立的单元测试,它证明了解决方案:

@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);
    }
  }
}

更好的计划

现在您已经提供了一个具体的示例,我可以向您展示如何在 Java 中进行测试,而无需求助于我上面的 JMock 黑客。

首先,您发布的一些 Java 版本:

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);
  }    
}

然后是一个测试,它练习了课程的所有有用功能,而不是诉诸于你最初要求的东西。

@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());
  }
}
于 2012-09-04T13:45:59.163 回答
0

JMock 不支持您的用例(或我在 java 中知道的任何其他模拟框架)。

我脑子里有个小声音说你正在尝试做的事情并不理想,而且你的单元测试可能太复杂了(也许它测试了太多的代码/逻辑?)。我看到的一个问题是,您不知道这些模拟需要返回哪些值,并且您正在插入其他东西,这可能会使每次运行都无法重现。

于 2012-08-31T09:46:26.757 回答