20

Consider the following test:

public void FooTest(decimal? val)
{
    Check.That(true).IsTrue();
}

I want to run this test with extreme values (i.e. MaxValue and MinValue).

[TestCase(decimal.MaxValue)]

This outputs the following error : An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

[TestCase(79228162514264337593543935)]

I get this one now : Integral constant is too large

One last desperate try:

[TestCase(79228162514264337593543935M)]

Obviously I get this one because of the cast : An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

How does one write a unit test with decimal.MaxValue as a parameter? I could write a specific test for this problematic case but I would like to know if there's a way to write a TestCase like this.

Max
  • 3,453
  • 3
  • 32
  • 50

3 Answers3

19

Now that all others have said, why this problem occurs, your code should use the TestCaseSource attribute to write your test:

private static object[] TestValues = 
{
    new object[]{ Decimal.MaxValue },
    new object[]{ Decimal.MinValue }
};

[TestCaseSource("TestValues")]
public void FooTest(decimal value)
{
    Assert.That(value, Is.EqualTo(Decimal.MaxValue));
}
Oliver
  • 43,366
  • 8
  • 94
  • 151
  • Upvoted. If you find it nicer (no boxing etc. on _your_ side), you could write `private static decimal[][] testValues = { new[] { Decimal.MaxValue }, new[] { Decimal.MinValue }, };` instead, if I remember correctly (did not test it right now). – Jeppe Stig Nielsen Aug 04 '14 at 13:03
  • @JeppeStigNielsen: That's right, but this won't work if you have multiple arguments and the performance penalty for boxing at this point is not really measureable, why i normally stick to the object array. – Oliver Aug 05 '14 at 05:40
7

Decimal.MaxValue is not a constant, it is a static readonly field. Which means you can't use it in attributes as attributes require constants. You'll have to hard code it.

Visual studio will pretend it as a const but it is actually not.

bool isConstant = typeof (decimal)
    .GetField("MaxValue", BindingFlags.Static | BindingFlags.Public)
    .IsLiteral;
//isConstant will be false :(
Community
  • 1
  • 1
Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • 1
    In short visual studio cheats us :p – Sriram Sakthivel Aug 04 '14 at 09:37
  • 1
    Why is it const here: http://referencesource.microsoft.com/#mscorlib/system/decimal.cs ?? – T_D Aug 04 '14 at 09:41
  • 3
    @T_D Well, Jon has an answer [here](http://stackoverflow.com/questions/21502561/decimal-source-code-from-ms-source-will-it-build) – Sriram Sakthivel Aug 04 '14 at 09:41
  • This might be correct (although reference source disagrees) but even `[TestCase(1m)]` fails. Decimals simply can't be used in this context. The compiled IL code treats decimals differently to ints. – Dirk Aug 04 '14 at 09:44
  • @SriramSakthivel Very nice answer also to the specific question of OP – T_D Aug 04 '14 at 09:48
  • Misleading. In C# terms, certainly `decimal.MaxValue` is a `const`. Otherwise, how would the following C# be allowed: `const decimal SriramsNumber = decimal.MaxValue; /* OK */`? The type `System.Decimal` cannot appear as argument in any attribute application, though, but that is something else. If you look at the generated IL, you will see that there is an attribute `System.Runtime.CompilerServices.DecimalConstantAttribute`. That is how a constant of type `decimal` looks in CLR. However, constants of _primitive_ types (CLR) are `static literal` in the IL, but `decimal` constants cannot be. – Jeppe Stig Nielsen Aug 04 '14 at 12:53
  • @JeppeStigNielsen Who cares about c# terms when question is about `decimal.MaxValue` and `Attribute` neither of them is c# property. Both are properties of .Net framework, for the clr my answer is right. c# compiler does the magic by automatically converting the `const` field to `readonly` may be other compilers don't do that. Also questioner didn't asked in c#'s perspective. Nevermind about the downvote, your comment is likely to help future readers. Thanks for the interest. – Sriram Sakthivel Aug 04 '14 at 13:32
  • It is correct that `System.Decimal::MaxValue` from a CLR point of view is not `literal` (you used the C# term `const`), instead it is `initonly` (you used the C# keyword `readonly`). But surely the struct `System.Decimal` is written in C#, and in that language the "constant" (C# terminology) `MaxValue` must necessarily be written as `public const decimal MaxValue ...`. If it had been `public static readonly decimal MaxValue ...` in C#, this member would have entirely different semantics in C#. – Jeppe Stig Nielsen Aug 04 '14 at 20:46
  • The reason why I found you answer a bit misleading, is that it comes close to saying that this particular member `MaxValue` is not constant/literal enough. But it is as constant as any `System.Decimal` can be. Your answer is almost like saying "The reason why `[TestCase(79228162514264337593543935M)]` (or `[TestCase(0M)]`) does not work is that `79228162514264337593543935M` is not a constant!" In some sense it is technically correct, but not the most optimal explanation. However, it is helpful, after all, to understand why a `const decimal` is not `.IsLiteral`. – Jeppe Stig Nielsen Aug 04 '14 at 20:51
  • @JeppeStigNielsen I see you're pedantic. I used c# term readonly because many of the people may not be knowing term `InitOnly` and same thing for `IsLiteral` also. I believe you can give a deep insight in new answer, If you provide a accurate explanation if your answer you own my upvote.. – Sriram Sakthivel Aug 05 '14 at 06:33
  • Downvoted, it does not answer the question, it only explains why. Oliver posted a working solution. – MeanGreen Sep 24 '14 at 09:41
  • @MeanGreen [You may need to read this](http://en.wiktionary.org/wiki/give_a_man_a_fish_and_you_feed_him_for_a_day;_teach_a_man_to_fish_and_you_feed_him_for_a_lifetime) – Sriram Sakthivel Sep 24 '14 at 10:39
7

It doesn't matter whether you try to use [TestCase(Decimal.MaxValue)] or use a literal like [TestCase(1m)]. Neither will work.

According to the C# specification (17.1.3, Attribute parameter types):

The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:
• One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
• The type object.
• The type System.Type.
• An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (§17.2).
• Single-dimensional arrays of the above types.

Notice the absence of decimal in the first list item.

The error message is a bit misleading because same specification also says that a decimal can be a constant expression (7.19).

But if you look at the IL code when creating a decimal you'll see that it actually invokes a constructor call: newobj System.Decimal..ctor. And that's unlike other literals, e.g. ldc.r8 33 33 33 33 33 33 F3 3F for var a = 1.2;.

Dirk
  • 10,668
  • 2
  • 35
  • 49