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

Support for partial mocking #141

Closed
dpeger opened this issue Jun 6, 2019 · 6 comments
Closed

Support for partial mocking #141

dpeger opened this issue Jun 6, 2019 · 6 comments

Comments

@dpeger
Copy link

dpeger commented Jun 6, 2019

With the latest reanimation of JMock are there any plans to add new features to JMock like partial mocking, mocking of static methods or mocking of private methods??

Especially partial mocking would be a great thing to have!

@olibye
Copy link
Member

olibye commented Jun 6, 2019

Can you give an example?

Partials:
Usually if you only need to mock a small part (a) of a larger contract (A) , it's an indication that you should break the code up where A is a composition of (a + the rest).

Where you can't change the code, because it's belongs to a third party, then you could use an adaptor to thin down the third party contract to just the part you need.

Privates:
Again, if the contract you're testing is so complicated that private implementation details need their own unit tests, consider composing the code of smaller unit testable bits.

Before java modules you could easily test package scope methods from other same package on the test side of the code branch. This is not so easy with module rules switched on.

JMock comes from world where we still believe in using interfaces. Byte code generators allowed us to mock concrete classes, but In many ways the lambda functional style has returned to using interfaces.

Compose your large behaviours from smaller ones and unit test those. You'll then find you don't need to unit test privates, as you'll inject the private behaviour as a function, and functions are mockable.

@dpeger
Copy link
Author

dpeger commented Jun 6, 2019

Of course you can always say "break up the code" in order to avoid the need for partial mocking. But on the other hand having a bunch of tiny classes which are only used in one single place doesn't improve code quality as well...

Given this (oversimplyfied) example

public class MockingExample {

    public static class ClassUnderTest {

        private final Dependency dependency;
        
        public ClassUnderTest(Dependency dependency) {
            this.dependency = dependency;
        }
        
        public String toBeTested() {
            String internalValue = internalMethod();
            // do something and return value - This behaviour should actually be tested
        }

        private String internalMethod() {
            String value = dependency.doSomething();
            return transformValue(value);
        }

        private String transformValue(String value) {
            // ...
        }
        
    }
    public static class Dependency {
        public String doSomething() {
            // ...
        }
    }
}

Given I want to test the functionality of ClassUnderTest.toBeTested() currently I will have to mock Dependency and setup expectations for Dependency.doSomething(). That is I actually need to know all the internals that happen inside internalMethod.

With partial mocking I could mock ClassUnderTest.internalMethod() directly without having to deal with the internals that much.

The example is very down stripped. In reality you probably have a chain of 2-3 private methods that each use mocked dependencies or do some minor modifications. Each of which would need to be inspected and the expectations would need to be defined according to the internal behaviour of these methods.

By making the internal methods package private, each method could be tested separately instead of implicitly testing the internals as it is done at the moment.

I know that this is not the true doctrine but this is how it works in practice - like it or not.

@olibye
Copy link
Member

olibye commented Jun 6, 2019

without having to deal with the internals that much.

The thing is, like it or not, Dependency. doSomething() is not internal, it's explicitly externally observable.

If you really want to ignore the side effects of doSomething(), then you could do the following and test ClassUnderTest.toBeTestedAfterSomethingDone().

public class MockingExample {

    public static class ClassUnderTest {

        private final Dependency dependency;
        
        public ClassUnderTest(Dependency dependency) {
            this.dependency = dependency;
        }
        
        public String toBeTested() {
            toBeTested(dependency.doSomething());
        }

        public static String toBeTestedAfterSomethingDone(String somethingDone) {
            String internalValue = internalMethod(somethingDone);
            // do something and return value - This behaviour should actually be tested
        }

        private static String internalMethod(String value) {
            // internal so pass this in String value = dependency.doSomething();
            return transformValue(value);
        }

        private static String transformValue(String value) {
            // ...
        }
        
    }
    public static class Dependency {
        public String doSomething() {
            // ...
        }
    }
}

However, when I say "want to ignore the side effects of doSomething()", I feel a bit naughty.
Simply doing this functionally equivalent refactoring means I'm forced to admit the method name toBeTestedAfterSomethingDone.

In reality you probably have a chain of 2-3 private methods

We call this chaining a train wreck: https://devcards.io/train-wreck
It breaks https://en.wikipedia.org/wiki/Law_of_Demeter

It's this very design that breaks encapsulation, requiring you to mock ever deeper objects and their return values. It's the design that's fragile and having to mock so many things should be the warning that the design should be improved.

In practise, and only because I have good unit test coverage, I do not like this code. I would have the courage to refactor it, as I'd be confident my tests will save me from myself tomorrow, and the code will be left in a better state. JMock isn't just about unit testing for the sake of it, it's about being confident to improve code by objective measures e.g. https://en.wikipedia.org/wiki/Law_of_Demeter

Mockito lets you get away with this sort of design as it fakes up return value. You may call that pragmatic, but in JMock's opinion it's sloppy.

@dpeger
Copy link
Author

dpeger commented Jun 7, 2019

As said, the above example is somewhat oversimplyfied. And with chaining I was using the wrong term actually. What I meant was that some private methods of ClassUnderTest might actually be calling other internal methods and these might call other internal methods again and in the second to third level in the internal call hierarchy the mocked dependency is accessed (again in real life there is not a single dependency but more like 4-5 in average that have to be mocked.)

That is to test a specific aspect of ClassUnderTest.toBeTested() I need to dig through the complete hierarchy to finally get to the point where dependency is accessed.

However I'll take your answer as a "No"

@olibye
Copy link
Member

olibye commented Jun 7, 2019

Correct. JMock is just being maintained, I'm not actively adding features.

Mockito is much better for people adding tests retrospectively.
See https://myshittycode.com/2014/03/13/mockito-effective-partial-mocking/

JMock is a test first library. In our opinion, you do not end up with this type of design problem if you actually write tests first. That's the JMock library opinion, and a view expressed by the original authors here http://www.growing-object-oriented-software.com/

Day to day I use Mockito on many projects, but I personally would still refactor rather than use Mockitos thenCallRealMethod approach. If you're adding tests to existing code and you have no tests the refactoring becomes difficult. In this case Mockito is more flexible and you have to carry this technical debt with you.

@olibye olibye closed this as completed Jun 7, 2019
@dpeger
Copy link
Author

dpeger commented Jun 7, 2019

Thanks for your feedback anyways 👍

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

2 participants