1+ """
2+ Unit tests for the Snake Water Gun game.
3+
4+ Tests cover:
5+ - All win/loss/tie combinations (3×3 matrix).
6+ - Case‑insensitivity and whitespace handling.
7+ - Invalid inputs.
8+ - The interactive ``main()`` loop (with mocked I/O).
9+ """
10+
11+ import random
12+ import pytest
13+ from io import StringIO
14+
15+ from snake_water_gun import play , main , VALID_CHOICES , WIN_CONDITIONS
16+
17+
18+ # ---------------------------------------------------------------------------
19+ # Helper: parametrised tests for play()
20+ # ---------------------------------------------------------------------------
21+
22+ # Map computer choice to expected outcome for a given player choice
23+ OUTCOMES = {
24+ # player chooses Snake
25+ ("Snake" , "Snake" ): "It's a tie!" ,
26+ ("Snake" , "Water" ): "You win!" ,
27+ ("Snake" , "Gun" ): "You lose!" ,
28+ # player chooses Water
29+ ("Water" , "Snake" ): "You lose!" ,
30+ ("Water" , "Water" ): "It's a tie!" ,
31+ ("Water" , "Gun" ): "You win!" ,
32+ # player chooses Gun
33+ ("Gun" , "Snake" ): "You win!" ,
34+ ("Gun" , "Water" ): "You lose!" ,
35+ ("Gun" , "Gun" ): "It's a tie!" ,
36+ }
37+
38+
39+ @pytest .mark .parametrize (
40+ "player_choice, computer_choice" ,
41+ [
42+ (p , c )
43+ for p in VALID_CHOICES
44+ for c in VALID_CHOICES
45+ ],
46+ )
47+ def test_all_outcomes (player_choice : str , computer_choice : str , monkeypatch ) -> None :
48+ """
49+ Verify that every possible combination returns the correct result string.
50+ """
51+ monkeypatch .setattr (random , "choice" , lambda _ : computer_choice )
52+
53+ result = play (player_choice )
54+ expected_outcome = OUTCOMES [(player_choice , computer_choice )]
55+ expected = (
56+ f"You chose { player_choice } , Computer chose { computer_choice } . "
57+ f"{ expected_outcome } "
58+ )
59+ assert result == expected
60+
61+
62+ @pytest .mark .parametrize (
63+ "raw_input, normalised" ,
64+ [
65+ ("snake" , "Snake" ),
66+ ("SNAKE" , "Snake" ),
67+ (" water " , "Water" ),
68+ ("GuN" , "Gun" ),
69+ ],
70+ )
71+ def test_case_insensitivity_and_whitespace (
72+ raw_input : str , normalised : str , monkeypatch
73+ ) -> None :
74+ """Input is normalised regardless of casing and surrounding spaces."""
75+ # Fix computer choice to something predictable
76+ monkeypatch .setattr (random , "choice" , lambda _ : "Gun" )
77+
78+ result = play (raw_input )
79+ # We expect the result string to contain the normalised player choice
80+ assert f"You chose { normalised } " in result
81+
82+
83+ @pytest .mark .parametrize (
84+ "invalid_input" ,
85+ [
86+ "" ,
87+ " " ,
88+ "rock" ,
89+ "paper" ,
90+ "scissors" ,
91+ "snakes" ,
92+ "gunwater" ,
93+ "123" ,
94+ ],
95+ )
96+ def test_invalid_input_returns_error (invalid_input : str ) -> None :
97+ """Invalid choices produce an error message."""
98+ result = play (invalid_input )
99+ assert result .startswith ("Invalid choice:" )
100+
101+
102+ # ---------------------------------------------------------------------------
103+ # Tests for main() interactive loop
104+ # ---------------------------------------------------------------------------
105+
106+ def test_main_quit_immediately (monkeypatch , capsys ) -> None :
107+ """Typing 'quit' right away exits the loop with a goodbye message."""
108+ monkeypatch .setattr ("sys.stdin" , StringIO ("quit\n " ))
109+ main ()
110+ captured = capsys .readouterr ()
111+ assert "Thanks for playing. Goodbye!" in captured .out
112+
113+
114+ def test_main_one_round_then_quit (monkeypatch , capsys ) -> None :
115+ """Play a single round and then quit."""
116+ inputs = StringIO ("Snake\n quit\n " )
117+ monkeypatch .setattr ("sys.stdin" , inputs )
118+ # Force computer choice to make assertion deterministic
119+ monkeypatch .setattr (random , "choice" , lambda _ : "Water" )
120+
121+ main ()
122+ captured = capsys .readouterr ()
123+ assert "You chose Snake, Computer chose Water. You win!" in captured .out
124+ assert "Thanks for playing. Goodbye!" in captured .out
0 commit comments