/*
 * Copyright (c) 2007 Mockito contributors
 * This program is made available under the terms of the MIT License.
 */
package org.mockitousage.debugging;

import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.invocation.DescribedInvocation;
import org.mockito.listeners.InvocationListener;
import org.mockito.listeners.MethodInvocationReport;

import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.*;


/**
 * Ensures that custom listeners can be registered and will be called every time
 * a method on a mock is invoked.
 */
public class InvocationListenerCallbackTest {

    // Cannot use a mockito-mock here: during stubbing, the listener1 will be called
    // and mockito will confuse the mocks.
    private RememberingListener listener1 = new RememberingListener();
    private RememberingListener listener2 = new RememberingListener();

    @Test
    public void should_call_single_listener_when_mock_return_normally() throws Exception {
        // given
        Foo foo = mock(Foo.class, withSettings().invocationListeners(listener1));
        willReturn("basil").given(foo).giveMeSomeString("herb");

        // when
        foo.giveMeSomeString("herb");

        // then
        assertThatHasBeenNotified(listener1, "basil", getClass().getSimpleName());
    }

    @Test
    public void should_call_all_listener_when_mock_return_normally() throws Exception {
        // given
        Foo foo = mock(Foo.class, withSettings().invocationListeners(listener1, listener2));
        given(foo.giveMeSomeString("herb")).willReturn("rosemary");

        // when
        foo.giveMeSomeString("herb");

        // then
        assertThatHasBeenNotified(listener1, "rosemary", getClass().getSimpleName());
        assertThatHasBeenNotified(listener2, "rosemary", getClass().getSimpleName());
    }


    @Test
    public void should_call_all_listener_when_mock_throws_exception() throws Exception {
        // given
        InvocationListener listener1 = mock(InvocationListener.class, "listener1");
        InvocationListener listener2 = mock(InvocationListener.class, "listener2");
        Foo foo = mock(Foo.class, withSettings().invocationListeners(listener1, listener2));
        doThrow(new OvenNotWorking()).when(foo).doSomething("cook");

        // when
        try {
            foo.doSomething("cook");
            fail("Exception expected.");
        } catch (OvenNotWorking actualException) {
            // then
            InOrder orderedVerify = inOrder(listener1, listener2);
            orderedVerify.verify(listener1).reportInvocation(any(MethodInvocationReport.class));
            orderedVerify.verify(listener2).reportInvocation(any(MethodInvocationReport.class));
        }
    }

    static class OvenNotWorking extends RuntimeException { }

    private void assertThatHasBeenNotified(RememberingListener listener, Object returned, String location) {
        assertThat(listener.returnValue).isEqualTo(returned);
        assertThat(listener.invocation).isNotNull();
        assertThat(listener.locationOfStubbing).contains(getClass().getSimpleName());
    }

    private static class RememberingListener implements InvocationListener {
        DescribedInvocation invocation;
        Object returnValue;
        String locationOfStubbing;

        public void reportInvocation(MethodInvocationReport mcr) {
            this.invocation = mcr.getInvocation();
            this.returnValue = mcr.getReturnedValue();
            this.locationOfStubbing = mcr.getLocationOfStubbing();
        }
    }
}
