In this situation, how valuable is it that we test every permutation of the constructor arguments? How would we test the difference between a ttl=60 and ttl=120? In the case of JWT we can decode the output and verify exact matches, but how valuable would that be? In my opinion, it is far more valuable to:

Defines what configuration is necessary to generate and validate tokens.

Ensure that token generation uses configuration correctly.

Starting from this position it is clear that the configuration would be better defined as a value object than as raw values. This changes our signature to:

Now we have a clearly defined value object that can be reused for both generation and validation without having to duplicate the values. If we need to add more options to the configuration we can do so without worrying that extended classes will no longer match signatures. And testing the generator becomes much easier because we can provide a mock object of the configuration to assert that the correct values are used at the correct time.

Binary Class Pattern

What I conclude from working through this particular code review and my own projects is that we can write better code by choosing to use only two types of objects:

Value Objects, constructed only with scalar values.

Collaborator Objects, constructed only with other objects.

Conclusion

By following a binary class pattern our code becomes easier to understand and more adaptable to change while also being more robust. We gain the ability to do independent testing of operation and structure while maintaining a clearer separation of concerns.