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

import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.exceptions.base.MockitoException;
import org.mockitousage.IMethods;
import org.mockitousage.MethodsImpl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static junit.framework.Assert.assertEquals;
import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.AdditionalAnswers.delegatesTo;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;

@SuppressWarnings("unchecked")
public class StubbingWithDelegateTest {
   public class FakeList<T> {
        private T value;
        
        public T get(int i) {
            return value;
        }
        
        public T set(int i, T value) {
            T oldValue = value;
            this.value = value;
            return oldValue;
        }
        
        public int size() {
            return 10;
        }
        
        public ArrayList<T> subList(int fromIndex, int toIndex) {
            return new ArrayList<T>();
        }
    }
    
    public class FakeListWithWrongMethods<T> {
        public double size() {
            return 10;
        }
        
        public Collection<T> subList(int fromIndex, int toIndex) {
            return new ArrayList<T>();
        }
    }
	
	@Test
	public void when_not_stubbed_delegate_should_be_called() {
		List<String> delegatedList = new ArrayList<String>();
		delegatedList.add("un") ;

		List<String> mock = mock(List.class, delegatesTo(delegatedList)) ;

		mock.add("two") ;

        assertEquals(2, mock.size());
	}

	@Test
	public void when_stubbed_the_delegate_should_not_be_called() {
		List<String> delegatedList = new ArrayList<String>();
		delegatedList.add("un") ;
		List<String> mock = mock(List.class, delegatesTo(delegatedList)) ;

		doReturn(10).when(mock).size();

		mock.add("two") ;

		assertEquals(10, mock.size());
        assertEquals(2, delegatedList.size());
	}

	@Test
	public void delegate_should_not_be_called_when_stubbed2() {
		List<String> delegatedList = new ArrayList<String>();
		delegatedList.add("un") ;
		List<String> mockedList = mock(List.class, delegatesTo(delegatedList)) ;

		doReturn(false).when(mockedList).add(Mockito.anyString()) ;

        mockedList.add("two") ;

		assertEquals(1, mockedList.size()) ;
		assertEquals(1, delegatedList.size()) ;
	}

    @Test
    public void null_wrapper_dont_throw_exception_from_org_mockito_package() throws Exception {
        IMethods methods = mock(IMethods.class, delegatesTo(new MethodsImpl()));

        try {
            byte b = methods.byteObjectReturningMethod(); // real method returns null
            fail();
        } catch (Exception e) {
            assertThat(e.toString()).doesNotContain("org.mockito");
        }
    }
    
    @Test
    public void instance_of_different_class_can_be_called() {
        List<String> mock = mock(List.class, delegatesTo(new FakeList<String>()));
        
        mock.set(1, "1");
        assertThat(mock.get(1).equals("1"));
    }
    
    @Test
    public void method_with_subtype_return_can_be_called() {
        List<String> mock = mock(List.class, delegatesTo(new FakeList<String>()));
        
        List<String> subList = mock.subList(0, 0);
        assertThat(subList.isEmpty());
    }
    
    @Test
    public void calling_missing_method_should_throw_exception() {
        List<String> mock = mock(List.class, delegatesTo(new FakeList<String>()));
        
        try {
            mock.isEmpty();
            fail();
        } catch (MockitoException e) {
            assertThat(e.toString()).contains("Methods called on mock must exist");
        }
    }
    
    @Test
    public void calling_method_with_wrong_primitive_return_should_throw_exception() {
        List<String> mock = mock(List.class, delegatesTo(new FakeListWithWrongMethods<String>()));
        
        try {
            mock.size();
            fail();
        } catch (MockitoException e) {
            assertThat(e.toString()).contains("Methods called on delegated instance must have compatible return type");
        }
    }
    
    @Test
    public void calling_method_with_wrong_reference_return_should_throw_exception() {
        List<String> mock = mock(List.class, delegatesTo(new FakeListWithWrongMethods<String>()));
        
        try {
            mock.subList(0, 0);
            fail();
        } catch (MockitoException e) {
            assertThat(e.toString()).contains("Methods called on delegated instance must have compatible return type");
        }
    }

    @Test
    public void exception_should_be_propagated_from_delegate() throws Exception {
        final RuntimeException failure = new RuntimeException("angry-method");
        IMethods methods = mock(IMethods.class, delegatesTo(new MethodsImpl() {
            @Override
            public String simpleMethod() {
                throw failure;
            }
        }));

        try {
            methods.simpleMethod(); // delegate throws an exception
            fail();
        } catch (RuntimeException e) {
            assertThat(e).isEqualTo(failure);
        }
    }
}
