This content originally appeared on DEV Community 👩‍💻👨‍💻 and was authored by etrandafir93
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;
}
}
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);
}
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;
}
}
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);
}
}
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());
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));
}
}
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);
}
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.
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!
This content originally appeared on DEV Community 👩‍💻👨‍💻 and was authored by etrandafir93
etrandafir93 | Sciencx (2023-01-28T15:46:11+00:00) MVC Violation Ruined His Tic-Tac-Toe Game!. Retrieved from https://www.scien.cx/2023/01/28/mvc-violation-ruined-his-tic-tac-toe-game/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.