Card Interaction Testing
This document explains the approach for testing card interactions in Rummage. It covers how to create robust tests for card effects, interactions between cards, and complex game scenarios.
Testing Framework
Rummage provides a flexible testing framework centered around the TestScenario
struct, which simplifies setting up test environments for card interactions. This framework allows developers to:
- Create players with custom life totals
- Add cards to specific zones (hand, battlefield, graveyard, etc.)
- Play cards, including targeting
- Resolve the stack
- Verify game state after actions
Example Test
Here's a simple example of testing a Lightning Bolt:
#![allow(unused)] fn main() { #[test] fn lightning_bolt_deals_3_damage() { let mut test = TestScenario::new(); let player1 = test.add_player(20); let player2 = test.add_player(20); let bolt = test.add_card_to_hand("Lightning Bolt", player1); // Play Lightning Bolt targeting player2 test.play_card(bolt, Some(player2)); test.resolve_top_of_stack(); // Verify player2 lost 3 life assert_eq!(test.get_player_life(player2), 17); } }
Testing Complex Interactions
For complex card interactions, the testing framework supports:
- Stack Interactions: Testing cards that interact with the stack, like counterspells
- Zone Transitions: Verifying cards move between zones correctly
- Targeting: Testing complex targeting requirements and restrictions
- State-Based Actions: Verifying state-based actions resolve correctly
Example of testing a counterspell:
#![allow(unused)] fn main() { #[test] fn counterspell_counters_lightning_bolt() { let mut test = TestScenario::new(); let player1 = test.add_player(20); let player2 = test.add_player(20); let bolt = test.add_card_to_hand("Lightning Bolt", player1); let counterspell = test.add_card_to_hand("Counterspell", player2); // Play Lightning Bolt targeting player2 test.play_card(bolt, Some(player2)); // In response, player2 casts Counterspell targeting Lightning Bolt test.play_card(counterspell, Some(bolt)); // Resolve the stack from top to bottom test.resolve_top_of_stack(); // Counterspell resolves test.resolve_top_of_stack(); // Lightning Bolt tries to resolve but was countered // Verify player2 still has 20 life (Lightning Bolt was countered) assert_eq!(test.get_player_life(player2), 20); } }
Best Practices
When writing tests for card interactions, follow these best practices:
- Test the rule, not the implementation: Focus on testing the expected behavior according to MTG rules, not implementation details
- Cover edge cases: Test unusual interactions and edge cases
- Test interactions with different card types: Ensure cards interact correctly with different types (creatures, instants, etc.)
- Use realistic scenarios: Create test scenarios that mimic real game situations
- Document test intent: Clearly document what each test is verifying
Extending the Testing Framework
The testing framework is designed to be extensible. To add support for testing new card mechanics:
- Add new methods to the
TestScenario
struct - Implement simulation of the new mechanic
- Add verification methods to check the result
Future Enhancements
The testing framework is still under development. Planned enhancements include:
- Support for more complex targeting scenarios
- Better simulation of priority and the stack
- Support for testing multiplayer interactions
- Integration with snapshot testing
Related Documentation
- Effect Verification: How card effects are verified
- Interaction Testing: Testing complex card interactions
- Snapshot Testing: Using snapshots for card testing
- End-to-End Testing: Testing cards in full game scenarios