DEV Community

Cover image for MVC Violation Ruined His Tic-Tac-Toe Game!
Emanuel Trandafir
Emanuel Trandafir

Posted on • Originally published at Medium

MVC Violation Ruined His Tic-Tac-Toe Game!

1. Overview

My friend is working on a cool new VR game and he asked me to help with some tips and tricks on how to test it.
PS: I was kidding with the VR part, it will be a console tic-tac-toe game:

public class Player {
    public static final int MAX_NUMBER = 100;
    public static final int MIN_NUMBER = 10;
    private final Scanner scanner = new Scanner(System.in);

    public int getNextMove() {
        System.out.println("please, type your next move and hit <enter>");
        int input = scanner.nextInt();

        while(input < MIN_NUMBER || input > MAX_NUMBER) {
            System.out.println(String.format("the number must between %d and %d! try again...", MIN_NUMBER, MAX_NUMBER));
            input = scanner.nextInt();
        } 
        // other game-related logic here
        return input;
    }
}
Enter fullscreen mode Exit fullscreen mode

Even though it is possible to do some "hacks" and test this method, ugly tests may be symptoms of bad design - so it's always a good idea to refactor the code first.

2. MVC Violation

Firstly, there is no separation between the model, view, and controller. This leads to a lot of coupling between the game logic and the input that comes through the terminal.
The best way to eliminate his direct coupling is to apply the Dependency Inversion Principle. In this case, let's declare an interface that allows user communication. We'll call it "PlayerView":

public interface PlayerView {
    int readNextInt();
    void write(String message);
}
Enter fullscreen mode Exit fullscreen mode

The Player object will only know about this interface. At this point, we only depend on our own classes (we no longer use System.out, System.in, Scanner … etc):

public class Player {
    public static final int MAX_NUMBER = 100;
    public static final int MIN_NUMBER = 10;
    private final PlayerView view;

    public Player(PlayerView console) {
        this.view = console;
    }

    public int getNextMove() {
        view.write("please, type your next move and hit <enter>");
        int input = view.readNextInt();

        while(input < MIN_NUMBER || input > MAX_NUMBER) {
            view.write(String.format("the number must between %d and %d! try again...", MIN_NUMBER, MAX_NUMBER));
            input = view.readNextInt();
        }
        return input;
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Dependency Inversion

How do we make it work? Well, to make it work the same as before, we now need to create an implementation of the PlayerView interface and copy the old functionality:

public class ConsoleView implements PlayerView {
    private static final Scanner scanner = new Scanner(System.in);

    @Override
    public int readNextInt() {
        return scanner.nextInt();
    }

    @Override
    public void write(String message) {
        System.out.println(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

As a result, when the game will be initialized, we'll need to create the players based on a ConsoleView, like this:

Player player = new Player(new ConsoleView());
Enter fullscreen mode Exit fullscreen mode

4. Writing a Mock

What are the gains? After this inversion of the dependency, the Player class can be tested with ease. For instance, we can use a mocking library to mock the view and allow us to test the rest of the functionality in isolation. In java, mockito is a popular and powerful tool for this.
On the other hand, blindly using libraries and frameworks might make us lose sight of the bigger image. So, from time to time, it is better to code the solution ourselves instead of bringing in a 3rd party library.
Therefore, let's create a very simple version of a mock object. We can do this simply by writing a new implementation of the PlayerView interface:

public class MockView implements PlayerView {
    private List<Integer> mockedUserInputs = new ArrayList<>();
    private List<String> displayedMessages = new ArrayList<>();

    @Override
    public int readNextInt() {
        return mockedUserInputs.remove(0);
    }

    @Override
    public void write(String message) {
        displayedMessages.add(message);
    }

    public List<String> getDisplayedMessages(){
        return displayedMessages;
    }

    public void mockedUserInputs(Integer... values) {
        mockedUserInputs.addAll(Arrays.asList(values));
    }
}
Enter fullscreen mode Exit fullscreen mode

As we can see, we'll use two lists
mockedUserInputs will be specified in the test setup. Each time somebody will call readNextInt(), the mock will return the next value from the list.
displayedMessages only has a getter. This list will be used to store all the messages we are trying to print. This can be useful in case we want to check that we are displaying the messages correctly.

5. Unit Testing

Finally, let's use our tailor-made mock class and write some unit tests:

@Test
void shouldAskUserToEnterNextMove() {
    //given
    MockView mockedView = new MockView();
    mockedView.mockedUserInputs(11);
    TestablePlayer player = new TestablePlayer(mockedView);

    //when
    player.getNextMove();

    //then
    List<String> displayedMessages = mockedView.getDisplayedMessages();
    assertThat(displayedMessages)
        .containsExactly("please, type your next move and hit <enter>");
}

@Test
void givenInvalidInput_shouldAskUserToReEnterTheMove() {
    //given
    MockView mockedView = new MockView();
    mockedView.mockedUserInputs(5, 22);
    TestablePlayer player = new TestablePlayer(mockedView);

    //when
    player.getNextMove();

    //then
    assertThat(mockedView.getDisplayedMessages())
        .containsExactly(
            "please, type your next move and hit <enter>",
            "the number must between 10 and 100! try again...");
}

@Test
void shouldReturnUsersMove() {
    //given
    MockView mockedView = new MockView();
    mockedView.mockedUserInputs(44);
    TestablePlayer player = new TestablePlayer(mockedView);

    //when
    int userMove = player.getNextMove();

    //then
    assertThat(userMove)
        .isEqualTo(44);
}
Enter fullscreen mode Exit fullscreen mode

6. Conclusion

In this article, we've learned about the MVC design pattern. We applied the Dependency Inversion Principle (the "D" in "SOLID") to decouple the controller from the view.
Finally, we were able to test it and we learned how to create a very simple mock object to simulate the user interaction.

Image description

Thank You!

Thanks for reading the article and please let me know what you think! Any feedback is welcome.

If you want to read more about clean code, design, unit testing, functional programming, and many others, make sure to check out my other articles.

If you like my content, consider following or subscribing to the email list. Finally, if you consider supporting my blog and buy me a coffee I would be grateful.

Happy Coding!

Top comments (4)

Collapse
 
cicirello profile image
Vincent A. Cicirello

Nice writeup.

Here is a tip for the DEV site. I noticed near the end that you have a link to where you originally posted this. DEV has a feature that allows you to specify the canonical url in a case like this where you cross posted. You can find it in the settings for your post. Most of those who use the canonical link feature cross post here from a personal blog site, but you can specify a canonical to medium, etc as well. The benefit is that (a) it avoids search engines penalizing one or the other in results as duplicate content, and (b) it enables you to specify which one should get the credit in search rankings.

Collapse
 
etrandafir93 profile image
Emanuel Trandafir

Hello @cicirello, Thanks for reaching out! I found it now and updated :)

Collapse
 
cicirello profile image
Vincent A. Cicirello

You're welcome

Collapse
 
Sloan, the sloth mascot
Comment deleted