Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mock] Let's discuss mocking #24

Open
borisbrodski opened this issue Aug 12, 2012 · 20 comments
Open

[Mock] Let's discuss mocking #24

borisbrodski opened this issue Aug 12, 2012 · 20 comments

Comments

@borisbrodski
Copy link
Collaborator

Hello Sebastian,

I read about your wish to discuss mocking support within Jnario/Xteand. This is exactly one of my tasks at the moment :-)

I evaluated all available mock libraries. At the mean time the Jmockit is in my option the best one. The core features:

  • Mocking of everything including static methods, constructors, ....
  • Proving the mock class definition in the middle of the tested code without need to do anything
  • Two APIs to choose from

How do you plan to integrate the mock API? I would suggest to override some of the Xtend Nonterminals, like BlockExpression (or something like this) to introduce some new keywords ('mock', 'stub', ...).

What do you think?

@sebastianbenz
Copy link
Owner

I've been thinking about how to support mocking in Jnario for quite a while know, without coming to a final decision. First thing is that I am not sure yet whether we need an explicit syntax for creating mocks or whether providing an xtend specific wrapper is sufficient. For example, it should be possible to provide something like the Spock mocking syntax based on Xtend. For example:

val myMockedList = mock(typeof(List)) => [
add("something")
add("something else")
]

val myStub = stub(typeof(Stack)) => [
pop >> "something"
]

I think I will start with a library for Xtend and see how far I will get.

@borisbrodski
Copy link
Collaborator Author

Hello Sebastian,

I work currently on a such library: https://github.com/borisbrodski/xtend-jmockit :-)
I will commit some more stuff shortly. I add extension methods in the TDD loop

You will find the sand-box version of all extension methods below.

I already get most of the features to work. The only problem is the parameter matching.

For example:

  • fnc("a", "b", any, "c")
    and
    - fnc("a", any, "b", "c")

In the Java those calls can be perfectly differentiated, but with the Xtend not.
This is because the JMockit doesn't perform the byte code enrichment of the mock [] blocks. :-(

For this case I provided new "with" extension method, so it looks like this:

  • fnc(with("a"), with("b"), any, with("c"))
    and
    - fnc(with("a"), any, with("b"), with("c"))

If you don't use "any_", you don't need the "with_" methods.

The finest thing is, that you can use block (closures) to test parameters (Xtend):

mock [
  service.method(match [ startsWith("abc") ])
]

Cool, ne?

If you interested, you can help me on this small project. (I think, it would be a little bit better, to offer this kind of functionality also without Jnario.)

Regards,
Boris
 

PS
Do you speak german?

PPS
Here is my code from the sand box

    protected void stub(final Procedure1< Expectations > proc) {
        new NonStrictExpectations() {

            {
                proc.apply(this);
            }
        };
    }

    protected void mock(final Procedure1< Expectations > proc) {
        new Expectations() {

            {
                proc.apply(this);
            }
        };
    }

    protected void returns(Expectations expectations, Object result) throws Exception {
        Method method = Expectations.class.getDeclaredMethod("returns", Object.class);
        method.setAccessible(true);
        method.invoke(expectations, result);
    }

    protected void setResult(Expectations expectations, Object result) throws Exception {
        ActiveInvocations.addResult(result);
    }

    protected void setResult(Expectations expectations, Object firstObject, Object... remainingValues) throws Exception {
        Method method = Expectations.class.getDeclaredMethod("returns", Object.class, Object[].class);
        method.setAccessible(true);
        method.invoke(expectations, firstObject, remainingValues);
    }

    protected < T > T onInstance(Expectations expectations, T mockedInstance) throws Exception {
        Method method = Expectations.class.getSuperclass().getDeclaredMethod("onInstance", Object.class);
        method.setAccessible(true);
        method.invoke(expectations, mockedInstance);
        return mockedInstance;
    }

    protected void setTimes(Expectations expectations, int times) throws Exception {
        ActiveInvocations.times(times);
    }

    protected void setMaxTimes(Expectations expectations, int maxTimes) throws Exception {
        ActiveInvocations.maxTimes(maxTimes);
    }

    protected void setMinTimes(Expectations expectations, int minTimes) throws Exception {
        ActiveInvocations.minTimes(minTimes);
    }

    protected < T > T any(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return null;
    }

    protected < T > T any(Expectations expectations, Class< T > t) throws Exception {
        ActiveInvocations.addArgMatcher();
        return null;
    }

    protected String anyString(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return null;
    }

    protected Long anyLong(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return 0L;
    }

    protected Integer anyInt(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return 0;
    }

    protected Short anyShort(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return 0;
    }

    protected Byte anyByte(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return 0;
    }

    protected Boolean anyBoolean(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return false;
    }

    protected Character anyChar(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return '\0';
    }

    protected Double anyDouble(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return 0.0;
    }

    protected Float anyFloat(Expectations expectations) throws Exception {
        ActiveInvocations.addArgMatcher();
        return 0.0F;
    }

    protected < T > T match(Expectations expectations, Matcher< T > matcher) throws Exception {
        addExpectationMatcher(expectations, matcher);
        return null;
    }

    protected < T > T match(Expectations expectations, Class< T > t, Matcher< T > matcher) throws Exception {
        match(expectations, matcher);
        return null;
    }

    protected < T > T match(Expectations expectations, final Function1< T, Boolean > matcherFnc) throws Exception {
        addExpectationMatcher(expectations, new Function1< Object, Object >() {

            @OverRide
            @SuppressWarnings("unchecked")
            public Object apply(Object p) {
                return matcherFnc.apply((T) p);
            }
        });
        return null;
    }

    protected < T > T match(Expectations expectations, Class< T > t, final Function1< T, Boolean > matcherFnc)
        throws Exception {
        match(expectations, matcherFnc);
        return null;
    }

    protected int matchInt(Expectations expectations, Matcher< Integer > matcher) throws Exception {
        addExpectationMatcher(expectations, matcher);
        return 0;
    }

    protected int matchInt(Expectations expectations, final Function1< Integer, Boolean > matcherFnc) throws Exception {
        match(expectations, matcherFnc);
        return 0;
    }

    protected long matchLong(Expectations expectations, Matcher< Long > matcher) throws Exception {
        addExpectationMatcher(expectations, matcher);
        return 0;
    }

    protected long matchLong(Expectations expectations, final Function1< Long, Boolean > matcherFnc) throws Exception {
        match(expectations, matcherFnc);
        return 0;
    }

    protected short matchShort(Expectations expectations, Matcher< Short > matcher) throws Exception {
        addExpectationMatcher(expectations, matcher);
        return 0;
    }

    protected short matchShort(Expectations expectations, final Function1< Short, Boolean > matcherFnc)
        throws Exception {
        match(expectations, matcherFnc);
        return 0;
    }

    protected byte matchByte(Expectations expectations, Matcher< Byte > matcher) throws Exception {
        addExpectationMatcher(expectations, matcher);
        return 0;
    }

    protected byte matchByte(Expectations expectations, final Function1< Byte, Boolean > matcherFnc) throws Exception {
        match(expectations, matcherFnc);
        return 0;
    }

    protected float matchFloat(Expectations expectations, Matcher< Float > matcher) throws Exception {
        addExpectationMatcher(expectations, matcher);
        return 0;
    }

    protected float matchFloat(Expectations expectations, final Function1< Float, Boolean > matcherFnc)
        throws Exception {
        match(expectations, matcherFnc);
        return 0;
    }

    protected double matchDouble(Expectations expectations, Matcher< Double > matcher) throws Exception {
        addExpectationMatcher(expectations, matcher);
        return 0;
    }

    protected double matchDouble(Expectations expectations, final Function1< Double, Boolean > matcherFnc)
        throws Exception {
        match(expectations, matcherFnc);
        return 0;
    }

    protected boolean matchBoolean(Expectations expectations, Matcher< Boolean > matcher) throws Exception {
        addExpectationMatcher(expectations, matcher);
        return false;
    }

    protected boolean matchBoolean(Expectations expectations, final Function1< Boolean, Boolean > matcherFnc)
        throws Exception {
        match(expectations, matcherFnc);
        return false;
    }

    protected char matchCharacter(Expectations expectations, Matcher< Character > matcher) throws Exception {
        addExpectationMatcher(expectations, matcher);
        return 0;
    }

    protected char matchCharacter(Expectations expectations, final Function1< Character, Boolean > matcherFnc)
        throws Exception {
        match(expectations, matcherFnc);
        return 0;
    }

    protected < T > T with(Expectations expectations, T t) throws Exception {
        addExpectationMatcher(expectations, is(t));
        return null;
    }

    protected int with(Expectations expectations, int i) throws Exception {
        addExpectationMatcher(expectations, is(i));
        return 0;
    }

    protected long with(Expectations expectations, long i) throws Exception {
        addExpectationMatcher(expectations, is(i));
        return 0;
    }

    protected short with(Expectations expectations, short i) throws Exception {
        addExpectationMatcher(expectations, is(i));
        return 0;
    }

    protected byte with(Expectations expectations, byte i) throws Exception {
        addExpectationMatcher(expectations, is(i));
        return 0;
    }

    protected float with(Expectations expectations, float i) throws Exception {
        addExpectationMatcher(expectations, is(i));
        return 0;
    }

    protected double with(Expectations expectations, double i) throws Exception {
        addExpectationMatcher(expectations, is(i));
        return 0;
    }

    protected boolean with(Expectations expectations, boolean i) throws Exception {
        addExpectationMatcher(expectations, is(i));
        return false;
    }

    protected char with(Expectations expectations, char i) throws Exception {
        addExpectationMatcher(expectations, is(i));
        return '\0';
    }

    private < T > void addExpectationMatcher(Expectations expectations, Object matcher) throws Exception {
        getCurrectExpectationsPhase(expectations).addArgMatcher(HamcrestAdapter.create(matcher));
    }

    private RecordPhase getCurrectExpectationsPhase(Expectations expectations) throws Exception {
        Method method = Expectations.class.getDeclaredMethod("getCurrentPhase");
        method.setAccessible(true);
        return (RecordPhase) method.invoke(expectations);
    }

@sebastianbenz
Copy link
Owner

That looks nice, I really like the syntax how you create mocks. The only thing I don't like too much is the way how you declare returned values. I find this quite unintuitive (although I know that this is how JMockit handles it). I would prefer something like:

mock[
add("a") => true
add("b") => list(true, false)
]

But this should be possible by providing a subclass of Expectations, which overrides the '=>' operator, shouldn't it?

@borisbrodski
Copy link
Collaborator Author

Cool idea. I will try it out.

PS
Don't you think, that the "=>" operator is some kind over-overloaded:

  • with (new Entity => [ name = "name1" ])
  • assert (entity.name => "name 1")
  • and now mock?

@sebastianbenz
Copy link
Owner

In case of mocking and assertion I would say it is no problem, as the semantic are basically the same. You express what the result of this expression is. The redundancy with the "with" semantics is unfortunate, the problem was that this was introduced after I create the Jnario assertions.

On Friday, 28. September 2012 at 14:21, Boris Brodski wrote:

Cool idea. I will try it out.
PS
Don't you think, that the "=>" operator is some kind over-overloaded:
with (new Entity => [ name = "name1" ])
assert (entity.name (http://entity.name) => "name 1")
and now mock?


Reply to this email directly or view it on GitHub (#24 (comment)).

@borisbrodski
Copy link
Collaborator Author

I think, this not doing to function. The problem is, that the context

mock [
...
]

is not a context extending Expectations but receiving the instance of the Expectations implementation as an "it" parameter. This means, that the "assert" meaning of the operator => get linked.

Do you have an idea, how to overcome this?

@sebastianbenz
Copy link
Owner

Shouldn't something like

public static void mock(final Procedure1< ExpectationsWithArrow > proc) {
        new ExpectationsWithArrow() {

            {
                proc.apply(this);
            }
        };
    }

public class ExpectationsWithArrow extends Expectations{

  public <T> void operator_doubleArrow(T methodCall, T result){
     ...
  }

}

```java

do the trick?

@borisbrodski
Copy link
Collaborator Author

Unfortunately not.

The Xtend

mock [
service.call
]

translates by the Xtend to a something like

Procedure function1 = new Procedure() {
public void apply(ExpectationsWithArrow it) {
service.call();

    // context not inherited from ExpectationsWithArrow class, but from Procedure<?> interface
}

}
mock(function1);

This means, that the "service.call" line is not in the context of the ExpectationsWithArrow class. By the way, this is the reason, way the xtend-jmockit extension library is needed in the first place. If we could force Xtend to extend this anonymous block from the Expectations or any other class (and not an interface), the JMockit library would work with Xtend nearly out of the box.

Xtend [ ] block works with any interface with a single method to implement (like Comparator) but not with any abstract class with a single method to implement :-(

@borisbrodski
Copy link
Collaborator Author

Hello Sebastian,

please, take a look of the dynamic results with JMockit: https://github.com/borisbrodski/jmockit-xtend#DynamicReturnValue

I think, it looks good. Only constraint: max. 6 parameters.

@sebastianbenz
Copy link
Owner

Looks really good. The constraint is not a problem in my opinion. You should use your jnario specs to generate the documentation ;-). Have a look at:

https://github.com/bmwcarit/Jnario/blob/master/tests/org.jnario.tests/src/org/jnario/spec/tests/documentation/SpecTutorial.spec

for an example.

@borisbrodski
Copy link
Collaborator Author

Hello Sebastian,

I wrote a patch for the jmockit, so the JUnit rules get supported as well. Using it and some other workarounds I managed to marry JMockit + Jnario + JUnit rules. You can take a look of this discussion (about the JMockit patch)

https://groups.google.com/forum/?fromgroups=#!topic/jmockit-users/dD2YsZkzRoI

As mentioned there, I had to write a small workaround in order to get the instance of the test class, created by the Jnario. In Jnario in ExampleRunner you override createTest() method. In this method you don't call super preventing JMockit from saving the new instance of the test class. Could you fix this calling super within this method?

Another question is, would you like to declare JMockit the official mocking library for Jnario in the future?

@sebastianbenz
Copy link
Owner

Hi Boris,

could you please create a pull request for the change.

Thanks.

Sebastian

@borisbrodski
Copy link
Collaborator Author

I'm not sure, I understand the purpose of SpecCreator. The current implementation is:

  protected Object createTest() throws Exception {
    Object test = this.testBuilder.createSpec(getTestClass().getJavaClass());
    initializeSubjects(getTestClass(), test);
    return test;
  }

But object instantiation should be done using super.createTest(), that is:

public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
  ...
  protected Object createTest() throws Exception {
    return getTestClass().getOnlyConstructor().newInstance();
  }
  ...
}

Is it save to just call super instead of this.testBuilder.createSpec, like this?

  protected Object createTest() throws Exception {
    Object test = super.createTest();
    initializeSubjects(getTestClass(), test);
    return test;
  }

@borisbrodski
Copy link
Collaborator Author

I could try it out and see, if the tests stay green, in case you don't have time for this.

@sebastianbenz
Copy link
Owner

I am delegating the call so that you can use a different implementation for creating spec instances, e.g. Guice.

@borisbrodski
Copy link
Collaborator Author

Do you plan to support one mocking library officially? Could it be JMockit?

If so, could you please take a look at https://groups.google.com/forum/?fromgroups=#!topic/jmockit-users/dD2YsZkzRoI

The plans to make JMockit official Jnario mocking library could help adding JUnit rules support to the JMockit.
Personally, I use rules over Before/After methods, because of higher abstraction level. But it seams, that I failed to clarify my point of view to Rogerio. He finds JUnit rules useless.

Are you interested in all this?

PS
If JMockit not get patched, JMockit expectations and mocks will not be specifiable in JUnit rules and so can't be used together with extension methods. :(

@sebastianbenz
Copy link
Owner

Hi

On Friday, 19. April 2013 at 10:53, Boris Brodski wrote:

Do you plan to support one mocking library officially? Could it be JMockit?

I don't know yet. There are many mocking libraries out there each with different advantages and disadvantages. This is why I have a hard time deciding on an official library. Also, I would like to avoid adding a specific mocking library to jnario's dependencies.

If so, could you please take a look at https://groups.google.com/forum/?fromgroups=#!topic/jmockit-users/dD2YsZkzRoI
The plans to make JMockit official Jnario mocking library could help adding JUnit rules support to the JMockit.
Personally, I use rules over Before/After methods, because of higher abstraction level. But it seams, that I failed to clarify my point of view to Rogerio. He finds JUnit rules useless.

Hm. I don't really see the need for specifying expectations in rules either. For me they are an essential part of a test's "documentation". I need to be able to read the expectations in order to understand the behavior that is tested.

@borisbrodski
Copy link
Collaborator Author

I (partially) agree on expectations, but the mocks are very important. For example, I use UnitTestRule, where I stub out all the hibernate and EJB stuff, than I have PersistentTestRule that connects to the real database but mocks out EJB stuff (all test run locally testing HQL queries against real DB).

The point is: specifying a rule, you put your test in some special environment. This environment can be created with mocks and stubs. You don't always need to see exactly, how mocks and stubs are configured, as long the behave consistently emulating some test environment.

@oehme
Copy link
Contributor

oehme commented May 18, 2013

I think it is already possible to integrate any mocking framework using a combination of SpecCreator, extension methods and Junit rules. So I don't really see a need to add special support to Jnario. There are two problems with an "official" mocking library:

  1. It could scare away people who like another one
  2. It would leave Jnario at the mercy of that library's maintainer.

@borisbrodski
Copy link
Collaborator Author

I agree with you. I think Jnario could provide integration for multiple mocking libraries (in form of separate projects, like https://github.com/borisbrodski/jmockit-xtend). A list of such projects with URLs and invitation to add support for more libraries could be enough.

ghaith pushed a commit to ghaith/Jnario that referenced this issue Aug 13, 2017
When doubleclicking a word inside a richstring the whole richstring was
selected by the editor. After binding the correct
ITokenTypeToPartioningTypeMapper and an appropriate DoubleClickStrategy
Richstring-Tokens are handled by finding selecting the word under the
courser, instead of selecting the whole token.
ghaith pushed a commit to ghaith/Jnario that referenced this issue Aug 13, 2017
…kePartitioning

fix sebastianbenz#24 TokenTypePartitioning for richstrings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants