tiebin-zhang / powermock

Automatically exported from code.google.com/p/powermock
Apache License 2.0
0 stars 0 forks source link

whenNew when the constructor of the class has too many arguments #405

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
Sometimes, a constructor of class has a lot of arguements, in the setup of a 
test, we will have many lines that makes the test less readable like : 
PowerMockito.whenNew(A.class).withArguments(any(B.class), any(C.class), 
anyListOf(D.class), anyInt()).thenReturn(mockedObject);

Is it possible to enhance this method to have something similar to 
whenNew(A.class).withNoArguments() but for the other non-default constructor ?

For example: whenNew(A.class).withAnyArguments().thenReturn(mockedObject)

Original issue reported on code.google.com by mohamed....@gmail.com on 24 Oct 2012 at 11:42

GoogleCodeExporter commented 9 years ago
That seems like a nice feature actually. Are you interested in helping out and 
provide patch for this is would be really great!

Original comment by johan.ha...@gmail.com on 6 Nov 2012 at 5:42

GoogleCodeExporter commented 9 years ago
Hi Johan,

I am working on that, I will keep you updated as soon as I can :)

Regards,
Mohamed

Original comment by ayman92...@gmail.com on 7 Nov 2012 at 9:35

GoogleCodeExporter commented 9 years ago
Have you made any progress? Please just ask if you have any questions. I 
suppose that it can be a bit tricky to get into.

Original comment by johan.ha...@gmail.com on 29 Nov 2012 at 6:51

GoogleCodeExporter commented 9 years ago
Sorry for the delay.
In fact, in mockito, an invocation has necessarely a signature and arguments
Without these informations, I am unable to find in the mockrepository the 
instance I am looking for.
So I was trying to mock the first constructor of the class and store somewhere 
it's arguments, what do you think ?

Regards

Original comment by mohamed....@gmail.com on 1 Dec 2012 at 12:50

GoogleCodeExporter commented 9 years ago
I just got another idea, maybe it would work if we did something like this:

whenNew(A.class).withAnyArguments().thenReturn(mockedObject)

When "withAnyArguments" is invoked it will iterate over ALL constructors of A 
(I think Whitebox has some methods to help out with this) and for each 
constructor extract it's parameter types and store it in a hashmap (constructor 
-> param types) or something. Then for each constructor in the hashmap invoke:

whenNew(<constructor in hashmap>).withArguments(any(constructorParamType1) , 
any(constructorParamType2), .., any(constructorParamTypeN));

(Actually whenNew shouldn't be used since directly since we'll then end up with 
a cyclic package dependency, rather use something like "new 
ConstructorAwareExpectationSetup" instead).

This way we don't need to keep any additional state in the mock repository and 
we'll leverage on Mockito for flexible argument matching. Do you think this 
will work?

Original comment by johan.ha...@gmail.com on 2 Dec 2012 at 8:32

GoogleCodeExporter commented 9 years ago
I've actually tried to implement this myself. I made only two new tests in 
"samples.powermockito.junit4.whennew.WhenNewTest" (the two last ones). I think 
it works but it's not tested well enough. Please try it out and see if it works 
and pretty please help out and write some more automated tests :)

Original comment by johan.ha...@gmail.com on 2 Dec 2012 at 2:24

GoogleCodeExporter commented 9 years ago
Awsome :)
I was working on that today, it is pretty similar to what I was doing :

private static <T> OngoingStubbing<T> createNewSubsituteMock(Class<T> type)
            throws Exception {
        if (type == null) {
            throw new IllegalArgumentException("type cannot be null");
        }

        final Class<T> unmockedType = (Class<T>) WhiteboxImpl
                .getUnmockedType(type);
        /*
         * Check if this type has been mocked before
         */
        NewInvocationControl<OngoingStubbing<T>> newInvocationControl = (NewInvocationControl<OngoingStubbing<T>>) MockRepository
                .getNewInstanceControl(unmockedType);
        if (newInvocationControl == null) {
            InvocationSubstitute<T> mock = MockCreator.mock(
                    InvocationSubstitute.class, false, false, null, null,
                    (Method[]) null);
            newInvocationControl = new MockitoNewInvocationControl(mock);
            MockRepository.putNewInstanceControl(type, newInvocationControl);
            MockRepository
                    .addObjectsToAutomaticallyReplayAndVerify(WhiteboxImpl
                            .getUnmockedType(type));
        }

        OngoingStubbing<T> expectSubstitutionLogic = null;
        Constructor<?>[] allConstructors = WhiteboxImpl
                .getAllConstructors(unmockedType);
        for (Constructor<?> constructor : allConstructors) {
            List<Object> arguments = new ArrayList<Object>();
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            for (Class<?> clazz : parameterTypes) {
                arguments.add(Mockito.any(clazz));
            }
            expectSubstitutionLogic = newInvocationControl.expectSubstitutionLogic(arguments);

        }

        return expectSubstitutionLogic;
    }

Original comment by mohamed....@gmail.com on 2 Dec 2012 at 3:02

GoogleCodeExporter commented 9 years ago
I had some problems with it, I will take a look to 
DelegatingToConstructorsOngoingStubbing.
I will an svn update and make some tests to see if it works fine.
By the way, what about the same feature for methods as well ?
Something like :
when(mockedObject.foo(withAnyArguments()) or 
when(mockedObject).foo(withAnyArguments()) ?

Regards,

Original comment by mohamed....@gmail.com on 2 Dec 2012 at 3:07

GoogleCodeExporter commented 9 years ago
Great, tell me if it works for you when you've tried it.

I suspect that the code you wrote don't work because if 
"newInvocationControl.expectSubstitutionLogic(arguments)" is called twice 
mockito will think that we're in the wrong state. Since what that code is 
actually doing is something similar to:

when(x.y());
when(x.z());

I.e. there's "thenReturn" before when(x.z());

For methods this already exists. You can use the "stubbing API" (you don't need 
mocks for this), e.g.

stub(method(X.class, "methodName").toReturn(someObject);

You can also replace and suppress methods etc (see 
org.powermock.api.support.membermodification.MemberModifier).

Original comment by johan.ha...@gmail.com on 2 Dec 2012 at 6:01

GoogleCodeExporter commented 9 years ago
It works fine for me :)

The only things with :
stub(method(X.class, "methodName").toReturn(someObject);
is that :
- It is not easy to read
- Need to add the class in the PrepareForTest annotation 
- Possible mistake in the name of the method as it is a string

Perhaps, it should be in Mockito at the first place

Original comment by mohamed....@gmail.com on 2 Dec 2012 at 6:31

GoogleCodeExporter commented 9 years ago
Great :)

If I would have designed that API today it would look different :). 

But without breaking the existing API it would be possible to do something like 
this (but it won't work right now):

stub(methodIn(X.class).methodToStub()).toReturn(x);

We're using something similar in the Awaitility project. 

How ever I don't think either 

when(mockedObject.foo(withAnyArguments()) 

nor 

when(mockedObject).foo(withAnyArguments()) 

will work since you only provide ONE argument matcher. What if foo has two 
parameters? You can't invoke with only one parameter (withAnyArguments()). 

Mockito already has support for this in the sense that you can do:

when(mockedObject.foo(any()) 

Original comment by johan.ha...@gmail.com on 2 Dec 2012 at 6:42

GoogleCodeExporter commented 9 years ago
If we have a class A that contains an overloaded method foo :
String foo(B1 b1)
String foo(B1 b1, B2 b2)
String foo(B1 b1, B2 b2, B3 b3)
....

when(mockedObject.foo(withAnyArguments()).thenReturn("Hello World");
should be equivalent to :

when(mockedObject.foo(any(B1)).thenReturn("Hello World");
when(mockedObject.foo(any(B1), any(B2)).thenReturn("Hello World");
when(mockedObject.foo(any(B1), any(B2), any(B3)).thenReturn("Hello World");
...

So basically, the idea is to get the list of all the method with same name, 
then for each, we call the previous instruction.

But I think this should be implemented in Mockito side.

Original comment by mohamed....@gmail.com on 4 Dec 2012 at 10:28

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
I have a class

public class A 
{
  public A(int , string , string){
     //Do something
    }
}

public class B
{
    public void someMethod()
    {
         A a = new A(int, String, String)
         //Do something
     }
}

Tests Case

@PrepareForTest(A.class)
public class BTests
{
   A aObj = Mockito.mock(A.class);
   PowerMockito.whenNew(A.class).withAnyArguments().thenReturn(aObj);

   B b= new B();
   b.someMethod() // When i do this it is actually calling the constructor of A. 

}

When calles someMethod , it actually called the constructor of A. 

Can you let me khow where i am making miskate. I am using testNg. 

Thnaks

Original comment by g.ranjan...@gmail.com on 21 May 2013 at 10:05

GoogleCodeExporter commented 9 years ago
To update #14, has this been resolved yet?

Original comment by nka...@gmail.com on 1 Apr 2015 at 6:33