I am designing an analytics application using domain driven design and test driven development. I am having difficulty modeling the following requirement:
The application shows a workitem's state. States are modeled as a composition of a superstate and a substate.
- Superstate can be any of the following: "A", "B", "C", "D"
- Substate can be any of the following: "X", "Y", "Y1", "Y2", "Z"
Each superstate has a set of valid substates:
- Valid substates of "A" are "Y", "Z".
- Valid substates of "C" are "X", "Y1", "Y2", "Z".
- All other superstates have the following valid substates: "X", "Y", "Z"
I devised a model which includes the following aggregate:

Workitem is the aggregate root and Workitem State is a value object.
A check at the instantiation of Workitem will ensure that the state has a valid combination of superstate and substate.
Since I'm following a TDD approach, the implementation of the state validation logic should be preceded by the definition of a corresponding test(psedudo-code):
#test module
func test_state_validity():
# tests all combinations of superstate and substate
for sup in SuperState:
for sub in SubState:
try:
s = State(sup, sub)
assert sub in get_allowed_substates(sup)
except InvalidStateException:
assert sub not in get_allowed_substates(sup)
Then i wrote the following implementation(psedudo-code):
# states module
enum SuperState:
A,
B,
C,
D
enum SubState:
X,
Y,
Y1,
Y2,
Z
class WorkitemState:
superstate: SuperState
substate: SubState
def create_state(superstate, substate):
if substate in get_allowed_substates(superstate):
return WorkitemState(superstate, substate)
else:
raise InvalidStateException
func get_allowed_substates(superstate):
switch superstate:
case SuperState.A:
return (SubState.Y, SubState.Z)
case SuperState.C:
return (SubState.X, SubState.Y1, SubState.Y2, SubState.Z)
case _:
return (SubState.X, SubState.Y, SubState.Z)
In compliance with DDD, the invariant has been enforced within the Domain Layer (in the aggregate root). In compliance with TDD, a corresponding test has been defined as well.
This made me realize there is significant overlap between the validation logic within the domain objects and unit tests (in my case I tried to keep it DRY by having both refer to the function get_allowed_substates).
My questions are:
- is writing tests for the domain model redundant, and thus be avoided? (in general and in this specific case)
- is it a sensible choice to test all possible combinations of superstate and substate, or should I rather restrict the scope of the test? e.g. all valid combinations plus a few edge cases such as {("A","X"), ("C","Y"), ("B", "Y1")}
Notes:
- differently from the example, my actual use case has ~100 possible super/substate combinations. The definition of superstates and their allowed substates is unlikely to change i nthe future. Most states tend to have the same set of allowed substates, with just a couple of excpetions
WorkitemStateis a simple valueobject intended for display. It is not an implementation of a state pattern and doesn't contain complex behaviour (this is just an analytics tool, state changes are managed by an external, pre-existing application)- the state of a workitem is retrieved from an external database, where it is stored as a string (e.g. "A Y"), which is parsed into a
WorkitemStateobject