import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import play.modules.springtester.PartialMock;
import play.modules.springtester.SpringMockitoUnitTestCase;
import play.modules.springtester.Subject;
import springtester.AgeCalculator;
import springtester.FavouriteThings;
import springtester.HeightCalculator;
import springtester.Person;
import springtester.Ruler;

public final class PersonTest extends SpringMockitoUnitTestCase {
    @Subject private Person subject;
    // Here we are injecting a mock that will expect a mocked invocation no matter what is called.
    @Mock(name = "ageCalculator") AgeCalculator mockAgeCalculator;
    // Here we are injecting another mock.
    @Mock(name = "heightCalculator") HeightCalculator mockHeightCalculator;
    // We are mocking out ruler and heightCalculator depends on ruler (even though we are mocking it out)
    @Mock(name = "ruler") Ruler mockRuler;
    // Here we are injecting a partial mock (i.e. spy) where the real method will be called unless it is stubbed out.
    @PartialMock(name = "favouriteThings") FavouriteThings partialMockFavouriteThings;

    @Test
    public void testCanVote() throws Exception {
        checkCanVote(false, 0);
        checkCanVote(false, 17);
        checkCanVote(true, 18);
        checkCanVote(true, 19);
        checkCanVote(true, 20);
    }

    @Test
    public void testSayFavouriteThings() throws Exception {
        // Here we are stubbing out the call to sport() only.  Normally this method will return football.  Now we stub it to return tennis.
        Mockito.when(partialMockFavouriteThings.sport()).thenReturn("tennis");
        // The call to favourite number should still return 10 (this is what is returned in the call to number().
        String actualFavouriteNumber = subject.sayFavouriteNumber();
        assertEquals("My favourite number is 10", actualFavouriteNumber);
        // The call to favourite sport will now return tennis instead of football since we stubbed that out above.
        String actualFavouriteSport = subject.sayFavouriteSport();
        assertEquals("My favourite sport is tennis", actualFavouriteSport);
    }

    @Test
    public void testGetHeight() throws Exception {
        // This test is checking that calling methods that don't use mocks injected using @Resource but instead the methods will
        // directly call Spring.getBeanOfType(Class) instead.
        checkGetHeight(100);
        checkGetHeight(0);
        checkGetHeight(-100);
    }

    private void checkGetHeight(int mockedHeight) {
        Mockito.when(mockHeightCalculator.calculate()).thenReturn(mockedHeight);
        int actual = subject.getHeight();
        assertEquals(mockedHeight, actual);
    }

    private void checkCanVote(boolean expected, int age) {
        Mockito.when(mockAgeCalculator.calculate("1-January-1980")).thenReturn(age);
        boolean actual = subject.canVote();
        assertEquals(expected, actual);
    }
}
