Параметр метода захвата в jMock для перехода к заштрихованной реализации

Я хочу добиться следующего поведения. Тест моего класса зависит от какого-либо другого класса, я хочу издеваться над этой зависимостью с помощью 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;
}

Итак, как я могу захватить параметр, который был передан методу, который насмехается, поэтому я мог бы передать их вместо этого в stubbed?

EDIT

Чтобы дать еще один пример, скажем, я хочу издеваться над зависимостью INameSource в следующем (С#) коде, чтобы протестировать класс 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 для С#. Я понимаю, что это не так просто, как это, поскольку делегаты отсутствуют в Java

Ответ 1

Решение от Duncan работает хорошо, но есть даже более простое решение, не прибегая к специальному совпадению. Просто используйте аргумент Invocation, который передается методу вызова CustomActions. При этом аргументе вы можете вызвать метод 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);
  }

Ответ 2

Как и Аугусто, я не уверен, что это хорошая идея в целом. Однако я не мог удержаться от небольшой игры. Я создал специальный матчи и настраиваемое действие, которое хранит и возвращает предоставленный аргумент.

Примечание: это далеко не готовый к производству код; Я просто повеселился. Здесь существует автономный unit test, который доказывает решение:

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

Ответ 3

JMock не поддерживает ваш случай использования (или любую другую насмешливую структуру, которую я знаю в java).

В моей голове немного голоса, в котором говорится, что то, что вы пытаетесь сделать, не идеально и что ваш unit test может быть сложным (может быть, он слишком много тестирует код/​​логику?). Одна из проблем, которые я вижу, заключается в том, что вы не знаете, какие значения должны вернуть эти макеты, и вы подключаете что-то еще, что может сделать каждый запуск непригодным.