This is a problem I have been trying to wrap my head around a couple months now. It has shown up again in a learning project I was working on last night, so I will use that as an example.
I am building an entity-component system, and have a Movement system that controls the movement of each entity on each turn.
class MovementSystem { private: vector<PositionComp> positions; vector<MoveAIComp> moveAIs; vector<vector<bool> > generateMap() { // Cycle through all positions and generates a 2D array // detailing whether each map space is occupied or not } void updateComps(vector<vector<bool> > map) { // For each entity generates a 3x3 array from map, centered // at that entity's position, passes it to // moveAI.getMove(surroundings), then updates the position } public: void update(Turn turn) { map = this->generateMap(); this->updateComps(map); } }; Now unit testing some of this functionality is fairly easy. I add a position and moveAI component to the system. The moveAI component is a mock object that returns a specific move that I specify in the test, I then call update on the sytem, and make sure that the position changed in the way is was supposed to.
The problem with this is I have skipped over the entire call to generateMap() and the first part of updateComps that creates the 3x3 array. Sure, I have tested that the position is properly updated based on what decision the moveAI makes, but I have not tested that the moveAI is getting the correct information to make that decision.
So far I have come up with a few ways to get around this:
- Change my mock
MoveAIComponentto cache the inputs togetMoveso I can check them in the test as well. - My
MovementSystemmay be doing too much, so I should split it into two, aMapSystemthat generates the map and updates a newSurroundingsComponent, then changeMovementSystemto use the surroundings, position, and moveAI, allowing tests onMapSystemto cover that functionality. - Leave testing of that functionality for integration tests, when I will be using real
MoveAIComponentobjects that do care about surroundings.
None of these options I particularly like. 1. greatly increases the complexity of both my tests and my mock class(es) since I need to add asserts to ensure the input is correct and cache every call to the mock objects. 2. feels like interface bloat. Since surroundings is only used inside MovementComponent, why should I make extra long-lived components to track what is actually temporary, transient, one use data? And finally 3. I dislike due to much the same reasons as 1, except worse. Tests could now fail for two reason: the surroundings is incorrect when passed to moveAI or the moveAI is making an unexpected decision.
So more generally, when you have a class that generates data that is only passed deeper into the stack and is never returned upward, what best way to test that functionality. Of course I am open to suggestions/redesigns that I have not thought of.
So more generally, when you have a class that generates data that is only passed deeper into the stack and is never returned upwardwell, something is consuming that data - test whether that is consumed right (or processed correctly).MovementSystemto depend on the logic inMoveAIso storing the inputs in a mock object then analyzing would probably be the simplest way to handle that.updatemethods would be called. Then I stubbed in tests forMovementSystem, naming them based on the functionality it would have. Then I implemented the first test, then implemented the logic inMovementSystemfor it to pass, then moved on to the next test. Perhaps that is where I messed up. When I made the first test pass, I implemented all of the generateMap logic which wasn't truly needed