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

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import org.junit.After;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.misusing.MissingMethodInvocationException;
import org.mockitousage.IMethods;
import org.mockitoutil.TestBase;

public class InvalidUsageTest extends TestBase {

    @Mock private IMethods mock;
    @Mock private IMethods mockTwo;

    @After
    public void resetState() {
        super.resetState();
    }

    @Test
    public void shouldRequireArgumentsWhenVerifyingNoMoreInteractions() {
        assertThatThrownBy(
                        () -> {
                            verifyNoMoreInteractions();
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContainingAll(
                        "Method requires argument(s)!",
                        "Pass mocks that should be verified, e.g:",
                        "    verifyNoMoreInteractions(mockOne, mockTwo);",
                        "    verifyNoInteractions(mockOne, mockTwo);");
    }

    @Test
    public void shouldRequireArgumentsWhenVerifyingNoInteractions() {
        assertThatThrownBy(
                        () -> {
                            verifyNoInteractions();
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContainingAll(
                        "Method requires argument(s)!",
                        "Pass mocks that should be verified, e.g:",
                        "    verifyNoMoreInteractions(mockOne, mockTwo);",
                        "    verifyNoInteractions(mockOne, mockTwo);");
    }

    @SuppressWarnings({"CheckReturnValue", "MockitoUsage"})
    @Test
    public void shouldNotCreateInOrderObjectWithoutMocks() {
        assertThatThrownBy(
                        () -> {
                            inOrder();
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContainingAll(
                        "Method requires argument(s)!",
                        "Pass mocks that require verification in order.",
                        "For example:",
                        "    InOrder inOrder = inOrder(mockOne, mockTwo);");
    }

    @Test
    public void shouldNotAllowVerifyingInOrderUnfamiliarMocks() {
        InOrder inOrder = inOrder(mock);
        assertThatThrownBy(
                        () -> {
                            inOrder.verify(mockTwo).simpleMethod();
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContainingAll(
                        "InOrder can only verify mocks that were passed in during creation of InOrder.",
                        "For example:",
                        "    InOrder inOrder = inOrder(mockOne);",
                        "    inOrder.verify(mockOne).doStuff();");
    }

    @Test
    public void shouldReportMissingMethodInvocationWhenStubbing() {
        when(mock.simpleMethod())
                .thenReturn("this stubbing is required to make sure Stubbable is pulled");
        assertThatThrownBy(
                        () -> {
                            when("".toString()).thenReturn("x");
                        })
                .isInstanceOf(MissingMethodInvocationException.class)
                .hasMessageContainingAll(
                        "when() requires an argument which has to be 'a method call on a mock'.",
                        "For example:",
                        "    when(mock.getArticles()).thenReturn(articles);",
                        "Also, this error might show up because:",
                        "1. you stub either of: final/private/equals()/hashCode() methods.",
                        "   Those methods *cannot* be stubbed/verified.",
                        "   Mocking methods declared on non-public parent classes is not supported.",
                        "2. inside when() you don't call method on mock but on some other object.");
    }

    @Test
    public void shouldNotAllowSettingInvalidCheckedException() {
        assertThatThrownBy(
                        () -> {
                            when(mock.simpleMethod()).thenThrow(new Exception());
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContainingAll(
                        "Checked exception is invalid for this method!",
                        "Invalid: java.lang.Exception");
    }

    @Test
    public void shouldNotAllowSettingNullThrowable() {
        assertThatThrownBy(
                        () -> {
                            when(mock.simpleMethod()).thenThrow(new Throwable[] {null});
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContaining("Cannot stub with null throwable!");
    }

    @SuppressWarnings("all")
    @Test
    public void shouldNotAllowSettingNullThrowableVararg() throws Exception {
        assertThatThrownBy(
                        () -> {
                            when(mock.simpleMethod()).thenThrow((Throwable) null);
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContaining("Cannot stub with null throwable!");
    }

    @Test
    public void shouldNotAllowSettingNullConsecutiveThrowable() {
        assertThatThrownBy(
                        () -> {
                            when(mock.simpleMethod()).thenThrow(new RuntimeException(), null);
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContaining("Cannot stub with null throwable!");
    }

    final class FinalClass {}

    @Test
    public void shouldNotAllowMockingFinalClassesIfDisabled() {
        assertThatThrownBy(
                        () -> {
                            mock(FinalClass.class);
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContainingAll(
                        "Cannot mock/spy class org.mockitousage.misuse.InvalidUsageTest$FinalClass",
                        "Mockito cannot mock/spy because :",
                        " - final class");
    }

    @SuppressWarnings({"CheckReturnValue", "MockitoUsage"})
    @Test
    public void shouldNotAllowMockingPrimitives() {
        assertThatThrownBy(
                        () -> {
                            mock(Integer.TYPE);
                        })
                .isInstanceOf(MockitoException.class)
                .hasMessageContainingAll(
                        "Cannot mock/spy int",
                        "Mockito cannot mock/spy because :",
                        " - primitive type");
    }

    interface ObjectLikeInterface {
        boolean equals(Object o);

        String toString();

        int hashCode();
    }

    @Test
    public void shouldNotMockObjectMethodsOnInterfaceVerifyNoInteractions() {
        ObjectLikeInterface inter = mock(ObjectLikeInterface.class);

        Object ignored = inter.equals(null);
        ignored = inter.toString();
        ignored = inter.hashCode();

        verifyNoInteractions(inter);
    }

    @Test
    public void shouldNotMockObjectMethodsOnClassVerifyNoInteractions() {
        Object clazz = mock(ObjectLikeInterface.class);

        Object ignored = clazz.equals(null);
        ignored = clazz.toString();
        ignored = clazz.hashCode();

        verifyNoInteractions(clazz);
    }
}
