Have you ever written an API or a method and expected it to be used a certain way, but then found people doing all kinds of bad things with your precious API that you never intended?
I was working on building an automated testing framework for a welfare system in which cases could be added and benefits run for a given month.
A typical scenario might be something like:
- Create a new case for this month.
- Add some people, do some things.
- Run benefits for this month.
- Run benefits for the next month. Something should happen because the child on the case is now 19.
Seems pretty straightforward until you consider what happens if someone hard codes dates into the test.
I am sure you can imagine plenty of scenarios in a situation like this where hard coded dates would eventually cause all kinds of problems.
The problem is?
If you create an API which takes in various dates, how do you ensure the dates passed in are calculated and not just hard coded?
Why convention doesn?t work
Your first response might be to create some documentation that describes the importance of making dates ?float? forward with time, and not be hard coded.
You might clearly describe how you should not hard code July 7th 1990 to be the birth date of a 20 year old for a test case.
You might give some guidelines of how to properly calculate ages.
But, you have no way of making sure the users of your API will follow those conventions or even read your document.
What if we do the hard work for them?
Why utilities are still not good enough
The next idea you might have is to create utilities that would do date conversions and calculate ages for the user of your API, so that they just have to remember to use them and all will be good.
This solution is better than one relying purely on convention, but still has some major flaws. Part of the problem of utility methods is that they are not self-discoverable. I?ve talked about before how DateUtilities like classes can get overlooked if you don?t know the utility class is there.
If you are relying on your users to go out and find your utility methods to make life easier for them, you are putting a large amount of responsibility in the wrong place. In my experience, it is very unlikely that anyone but yourself will actually use the utility methods.
You really need a way to stop them from being able to hard code dates.
How can you stop someone from hard coding dates?
Don?t accept dates in your API.
It is simple.
What? What do you mean? You can?t just stop accepting dates, you need dates. Not just dates, but integers, or strings. You can?t just not accept the bad data? or can you?
Consider wrapping the data type in another data type which creates your desired data type, but only using the convention or rules you desire.
Let me show you an example with the dates:
1: public void CreateCase(DateTime applicationDate)
2: {
3: DoSomethingWithTheDate(applicationDate);
4: }
5:
6: CreateCase(new DateTime(1990, 7, 8));
In the above example, you can see that I am directly taking a .NET DateTime object. You can also see that I am directly hard coding a date when calling this method.
Let?s see if we can fix that.
1: public void CreateCase(CaseDate applicationDate)
2: {
3: DoSomethingWithTheDate(CaseDate.ToDateTime());
4: }
5:
6: CreateCase(CaseDate.YearsAgo(20));
What happened?
Here is what we did:
- We created a custom type called CaseDate.
- We replaced all the external usages of DateTime with CaseDate.
- We provided methods on CaseDate which allow the creation of dates, only through the way we want to allow them to be created.
- Now we can easily prevent users of the API from being able to create hard coded dates. The API only will take CaseDate objects and internally creates DateTime objects from the CaseDate objects.
- We can add other methods to CaseDate to allow the creation of dates from x number of months ago or in the future, or any other valid creation method we want.
By doing this, we are restricting the valid set of inputs to our methods at compile time, not throwing exceptions at run time.
We are achieving the same kind of valuable input constrictions that we have been able to achieve with enumerations, except we are adding more complex restrictions than a simple list of selections.
Where can I use this?
Not just dates. There are many places where restricting the input to your method or API is going to simplify logic, protect against error conditions, and enforce constraints.
Consider using this pattern in some of these scenarios:
- You want to use an enumeration, but your list of possible choices is too large, or repetitive.
- Anywhere you are using a primitive type and that primitive type represents some more complex concept.
- Anywhere you have parameter validation or manipulation repeated in multiple methods.
- Anytime you process some input or repeat a series of steps to transform it before sending it as input.
Every time you are about to make a primitive type a parameter to a method you should ask yourself if it makes sense to wrap the primitive type into an class.
Remember, especially when creating an API, any time your method takes a primitive type, you lose the ability to constrain that input, and are forced to validate it instead.
Should you always wrap all primitive types? No, but it is another tool you can use to provide an extra layer of indirection between the logic of your API and your callers use of it.
I agree with your approach. Sadly, I see somebody coming along and adding a new static factory method called CaseDate.FromDateTime(DateTime dateTime). Then we would be back to square one. This is where a big stick would come in handy, heehee!
Maybe I’m missing something, but all you did was make it slightly harder for them to hard-code. So instead of:
CreateCase(new DateTime(1990, 7, 8));
they now just do:
CaseDate caseDate = new CaseDate();
caseDate.Date = new DateTime(1990, 7, 8);
CreateCase(caseDate);
Unless I’m missing something, all you’ve done is aggrevate the consuming developer and make your API harder to use. What’s the advantage, here?
I like your thinking on this. The CaseDate.YearsAgo(20) makes the code very readable.
However in order to make tests reproducible we normally use another solution for this problem. Rather than not accepting dates in the API we do not request dates in our code.
Instead, we always use an instance of an ITimestampProvider to get the date that we need. It has a method like GetUtcNow.
In most cases the tests inject a ConstantTimestampProvider that returns a constant time.
If we need time to pass, we can step the time forward on this stub.
This way we can easily control time and it it easy to verify that timestamps etc in the code are generated correctly, for example (vb.net):
dim justNow as new DateTime(2010,7,12)
dim timestampProvider as new ConstantTimestampProvider(justNow)
‘ ….
Assert.That(actual.LastUpdatedAt, [Is].EqualTo(justNow))
Best regards,
Martin Jul
Clojure / .NET developer
@Rob
Hi Rob,
I think you are missing it. caseDate.Date does not have a setter in my implementation.
The whole point is to make sure that raw dates are not used, instead one of the methods I provide on my custom date class is used to construct the date.
This prevents any dates from being hard-coded, since it is not possible to call the API without using the CaseDate class, and the date can not be set manually in the CaseDate class.
John, it seems you are describing the “value object” pattern from DDD. So instead of a decimal, create a Probability struct, instead of a double, create a Money struct, and so on. The idea is the value object shields the underlying value with invariants. Doing that, you also are describing what this value’s intent usage is. Which of the two below declaration tells more to the user of an API?
void SomeMethod(Decimal winProbability)
or
void SomeMethod(Probability winProbability)
In other words, you are not just “forcing” your API user, you are also helping him to understand your API.
It sounds like you are referencing DateTime.Now in your code, and since that value will change with different runs of the test fixture, hard-coded dates cannot represent fixed ages. (You imply this, but never state it, so let me know if that is an incorrect assessment).
The problem, then, is that you are unable to isolate the code being tested from the system clock. They are inherently coupled in such a way that all system clients must account for it. The CaseDate class merely treats the symptom, not the disease.
Were you to follow @Martin Jul’s suggestion (which I favor as well), you would be able to declare the value of “now” for any given test. Hard-coded dates are then perfectly acceptable because “now” is hard-coded as well.
Taking it further, what if you have a rule that takes effect in 2015, and you need to test it before it goes into production? Will you set your system clock ahead to run the test, and expect other devs to do the same? It makes more sense to abstract away the source of the “now” value, which is the intent of the ITimestampProvider suggestion.
(I realize this is not a use case of your system, but those kinds of rules are very relevant when giving general advice on handling dates.)
@Bryan Watts
Hi Bryan,
I understand what you are saying, but in this case I need the ages and application dates to be able to move up with the real time.
In this case I was creating an API for automated functional testing through the API.
Let me give you an example and see if this clears up what I was talking about.
Lets supposed I needed to created a brand new application with 19 year old on it.
I need the application date to be the current month… so CaseDate.CurrentMonth
I need the person to be 19 years old, so I use CaseDate.YearsAgo(19).
That test data will always be valid, regardless of the current year or time.
The idea is that the problem domain doesn’t really care about specific dates, it cares about relative dates.
Now, I agree with @Martin on not using DateTime in your code. You can synthetically produce dates, but that is a different matter than what I am addressing here.
@John Sonmez
Hi John,
I guess I don’t see where in your post you explain why hard-coded dates are undesirable. I understand your reasoning from that point forward, but I am unclear as to how you got there. Perhaps you could elaborate on why you are avoiding hard-coded dates in the first place?
I am unclear on the requirement “I need the ages and application dates to be able to move up with the real time”, which is probably why I am having difficulty understanding your position.
@Bryan Watts
You are right, I never really explained that part.
In that example, the problem would be that you can only run future eligibility months on the system, not past ones. So if you hard coded dates for creating a case for July 2010, and a 19 year old person, next month the test would not work.
Let me know if that makes it clear now. Sorry, I assumed too much domain knowledge.
@John Sonmez
No worries, just making sure we are on the same page.
Is it correct to say that you are using DateTime.Now directly in your code, and that is the reason why the tests won’t be valid next month?
@Bryan Watts
The problem is the actual running system that is under test is using the real system time. Even though you could make a synthetic time, it would not be accurate for the kind of tests the test code is testing.
The test code in this case is driving a Watij library which is driving a web browser to directly test the web pages, therefore the tests need to be able to operate against the current system.
Take a look at this for more information: http://www.slideshare.net/jsonmez/internal-ds-ls-for-automated-functional-testing
@John Sonmez
I see now you aren’t doing unit testing, you are doing integration testing and automation. I was trying to isolate components, which isn’t the point here. Thanks for clearing that up with me.