Search code examples
javajunitjunit4powermockito

PowerMockito verifyNew withArguments of an object and an array of objects


i am trying to test that a method creates an object. i have it almost working using PowerMockito.verifyNew().withArguments() however, the Arguments that are passed to the constructor are an object and an ArrayList of objects. the output of the test is: Actual

invocationSubstitute.performSubstitutionLogic(
1,
6,
11,
13,
[au.edu.sccs.csp3105.NBookingPlanner.Person@2449cff7],
au.edu.sccs.csp3105.NBookingPlanner.Room@62da83ed,
"description"
);

Expected

invocationSubstitute.performSubstitutionLogic(
1,
6,
11,
13,
[au.edu.sccs.csp3105.NBookingPlanner.Person@40bffbca],
au.edu.sccs.csp3105.NBookingPlanner.Room@42a9a63e,
"description"
);

i can see that the problem is the objects are the same type but not the same object, is there a way of saying the Expected object is of the correct type?

test:

@RunWith(PowerMockRunner.class)
@PrepareForTest({Planner.class, Meeting.class})

public class MonthInput {

Planner planner;

@Rule
    public final TextFromStandardInputStream systemInMock = emptyStandardInputStream();
@Rule
    public final ExpectedSystemExit exit = ExpectedSystemExit.none();

@SuppressWarnings("deprecation")
@Before
public void setup() throws Exception {
    Meeting meetingMock = Mockito.mock(Meeting.class);
    PowerMockito.whenNew(Meeting.class).withAnyArguments().thenReturn(meetingMock);     
}

@Test
public void MonthInputofless5() throws Exception {
    // make spy
    planner = Mockito.spy(Planner.class);

    //override main menu with do nothing
    Mockito.doNothing().when(planner).mainMenu();

    //provide inputs
    systemInMock.provideLines("1","6","11","13","ML13.218","Mark Colin","done","description");

    //set expected outputs
    ArrayList<Person> attendees = new ArrayList<Person>();
    attendees.add(new Person("Mark Colin"));
    Room where = new Room("ML13.218");

    //call the method
    planner.scheduleMeeting();

    //set passing terms     
    PowerMockito.verifyNew(Meeting.class).withArguments(1,6,11,13,attendees,where,"description");

}

Solution

  • Fix your code

    A verify simple fix on your classes: implement hashCode and equals on Person and Room so Powermock verification can actually compare two objects for equality and not just rely on the object reference.

    Fix your tests

    If you don't want to fix your code, but the test, you could either use a Mockito matcher (i.e. org.mockito.Matchers.eq or org.mockito.Matchers.any). But please note, that eq relies on equals and won't work unless you implement it (see above). But any would match any object of that type (surprise!)

    PowerMockito.verifyNew(Meeting.class)
                .withArguments(eq(1),eq(6),eq(11),eq(13), 
                              any(List.class),
                              any(Room.class),
                              eq("description"));
    

    If the actual value matters, you could use an ArgumentCapture instead of a matcher and check the captured value.

    Theoretically, it should look like this:

    final ArgumentCaptor<Person> personCaptor = forClass(Person.class);
    final ArgumentCaptor<Room> roomCaptor = forClass(Room.class);
     PowerMockito.verifyNew(Planner.class)
                 .withArguments(eq(1),eq(6),eq(11),eq(13), 
                      personCaptor.capture(), 
                      roomCaptor.capture(), 
                      eq("description"));
    
        final Person passedParam = personCaptor.getValue();
        //do your comparison here
    

    But I didn't get the capture example running, so maybe that's only possible with plain Mockito.

    Just don't!

    All that said, you should verify your overall approach. Using Whitebox-Testing may create very brittle tests and won't help much in refactoring and may further foster bad class design.

    The imprudent use of Powermockito is an anti-pattern. It's a very mighty tool for testing the untestable, i.e. badly designed legacy code from the dark ages of the internet where the homepages were handcrafted HTMLs and full of wobbly GIFs. Don't use it for new, greenfield projects. Just don't.

    Instead try focusing on the observable result of any action. Invoking the scheduleMeeting() method may produce any result that is more easy to check - it's the result that matters, not the way to get they. Trust me, not a single user gets happier when a constructor was called.

    Results could be

    • an instance of a meeting (as return value, that would be best in your example)
    • a state change in the planner (any getters?)
    • a state change in a downstream service / db
    • a written file
    • an output written to System.out