C# ValidationResult value equality assertion fails with Assert.AreEqual - c#

I have a simple ValidationRule implementation :
public class IntegerRangeRule : ValidationRule
{
public Int32 Max { get; set; }
public Int32 Min { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int integer;
if(string.IsNullOrEmpty((string)value))
{
return new ValidationResult(false, "Field cannot be empty");
}
if (Int32.TryParse((string) value, out integer))
{
if((integer < Min) || (integer > Max))
return new ValidationResult(false, string.Format("Enter a value between {0} and {1}.", Min, Max));
}
else
{
return new ValidationResult(false, "Illegal characters: " + (string)value);
}
return ValidationResult.ValidResult;
}
}
I wrote the following unit test for the Validation method:
[TestMethod()]
public void ValidateTest_InputTooSmall()
{
//Setup
var target = new IntegerRangeRule()
{
Max = 100,
Min = 1
};
var expected = new ValidationResult(false, "Enter a value between 1 and 100.");
//Exercise
var actual = target.Validate("0", null);
//Verify
Assert.AreEqual(expected.ErrorContent, actual.ErrorContent);
Assert.AreEqual(expected.IsValid, actual.IsValid);
Assert.AreEqual(expected.GetHashCode(), actual.GetHashCode());
Assert.AreEqual(expected, actual);
}
Here is the catch.
The first three assertions all pass. But the last one fails.
If I replace the
...
if (Int32.TryParse((string) value, out integer))
{
if((integer < Min) || (integer > Max))
return new ValidationResult(false, string.Format("Enter a value between {0} and {1}.", Min, Max));
}
...
with a constant string.
...
if (Int32.TryParse((string) value, out integer))
{
if((integer < Min) || (integer > Max))
return new ValidationResult(false, "Enter a value between 1 and 100.");
}
...
The last assertion will pass.
I dont understand what is causing the problem here. Thank you.

ValidationResult contains this in Equals:
return (IsValid == vr.IsValid) && (ErrorContent == vr.ErrorContent);
Since ErrorContent is an object, this is a reference comparison, not a value comparison. In your case you are generating a new string using String.Format and comparing that reference to a literal, so the result is always false. When you change it to the same literal string it passes because literals are interned by the CLR.
Meanwhile your testing framework is probably calling Object.Equals(object, object) inside its Assert.AreEqual method, which invokes String's overridden Equals method. This does a value comparison.

Related

C# How to convert time [ms, s, m, h, d] as string to seconds as int

I would like to know if there is a quick way (Maybe with TimeSpan) to convert a duration time formatted string like 17.24s or 20.79m or 1.3h to seconds format like 17 for 17.24s, 1260 for 20.79m and 4699 for 1.3h.
Thanks for suggestions.
Let's start from math:
20.79 minutes == 20.79 * 60 seconds == 1247 seconds
1.3 hours == 1.3 * 3600 seconds == 4680 seconds
I can't see 1260 or 4699, that's why I'll stick to simple math, I'll mark with \\TODO: the code which should be modified if you insist on different logic.
For different suffixes, let's extract model:
private static Dictionary<string, Func<double, TimeSpan>> s_Builders =
new Dictionary<string, Func<double, TimeSpan>>(StringComparer.OrdinalIgnoreCase) {
{ "", x => TimeSpan.FromSeconds(x)},
{ "s", x => TimeSpan.FromSeconds(x)},
//TODO: if you insist on 1260, put required logic here
{ "m", x => TimeSpan.FromMinutes(x)},
//TODO: if you insist on 4699, put required logic here
{ "h", x => TimeSpan.FromHours(x)},
{ "d", x => TimeSpan.FromDays(x)},
};
Time to implement TryMyParse method:
public static bool TryMyParse(string value, out TimeSpan result) {
result = default;
if (string.IsNullOrWhiteSpace(value))
return false;
string suffix = s_Builders
.Keys
.OrderByDescending(key => key.Length)
.FirstOrDefault(key => value.EndsWith(key, StringComparison.OrdinalIgnoreCase));
if (null == suffix)
return false;
else if (double.TryParse(value.Substring(0, value.Length - suffix.Length),
out double number)) {
try {
result = s_Builders[suffix](number);
return true;
}
catch (OverflowException) {
return false;
}
catch (ArgumentException) {
return false;
}
}
return false;
}
Finally, MyParse is very simple:
public static TimeSpan MyParse(string value) =>
TryMyParse(value, out var result)
? result
: throw new FormatException($"{value} is not a valid time");
Demo:
string[] tests = new string[] {
"17.24s",
"20.79m",
"1.3h",
};
string demo = string.Join(Environment.NewLine, tests
.Select(test => $"{test,6} :: {Math.Round(MyParse(test).TotalSeconds),4}"));
Console.Write(demo);
Outcome:
17.24s :: 17
20.79m :: 1247
1.3h :: 4680
Timespan does have a Parse method that accepts a string parameter but it uses a different input format than what you want. See more information here: https://learn.microsoft.com/en-us/dotnet/api/system.timespan.parse?view=netcore-3.1
You'll have to make your own parse method to convert a string input to a Timespan and use the TotalSeconds property to get the number of seconds.
If you go this route I would suggest starting by writing a unittest first containing all the inputs considered valid and their expected result. From there you can implement your parse method and verify it.
namespace StackOverflow
{
public class Tests
{
[Fact]
public void TestTimespanStringConversion()
{
// Test cases go here...
Assert.Equal(TimeSpan.FromHours(1.2), "1.20h".AsTimeSpan())
}
}
public static class StringExtensions
{
public static TimeSpan AsTimeSpan(this string input)
{
// Conversion logic goes here...
return new TimeSpan();
}
}
}
Thanks for the suggestions guys! I'm very appricate it. Here is my solution that workes for me:
private int timeParser(string pTime) {
int iResult = 0;
double dTime = 0.0;
NumberStyles style = NumberStyles.Number;
CultureInfo culture = CultureInfo.InvariantCulture;
if(pTime.Contains("ms")) {
if(Double.TryParse(pTime.Trim().Replace("ms", ""), style, culture, out dTime)) {
iResult = (int)Math.Round(TimeSpan.FromMilliseconds(dTime).TotalSeconds);
} else {
throw new FormatException("Unable to convert " + pTime);
}
} else if(pTime.Contains("s")) {
if(Double.TryParse(pTime.Trim().Replace("s", ""), style, culture, out dTime)) {
iResult = (int)Math.Round(TimeSpan.FromSeconds(dTime).TotalSeconds);
} else {
throw new FormatException("Unable to convert " + pTime);
}
} else if(pTime.Contains("m")) {
if(Double.TryParse(pTime.Trim().Replace("m", ""), style, culture, out dTime)) {
iResult = (int)Math.Round(TimeSpan.FromMinutes(dTime).TotalSeconds);
} else {
throw new FormatException("Unable to convert " + pTime);
}
} else if(pTime.Contains("h")) {
if(Double.TryParse(pTime.Trim().Replace("h", ""), style, culture, out dTime)) {
iResult = (int)Math.Round(TimeSpan.FromHours(dTime).TotalSeconds);
} else {
throw new FormatException("Unable to convert " + pTime);
}
} else if(pTime.Contains("d")) {
if(Double.TryParse(pTime.Trim().Replace("d", ""), style, culture, out dTime)) {
iResult = (int)Math.Round(TimeSpan.FromDays(dTime).TotalSeconds);
} else {
throw new FormatException("Unable to convert " + pTime);
}
} else {
throw new FormatException(pTime + " is not a valid timeformat");
}
return iResult;
}

Get a variable value from a method to another?

In this situation, I am trying to get the value of "FileSizeType", an integer variable, into the method that's under it "NomCategorie"and convert it to a string(that's what the comment says).
static int ChoisirCategory()
{
int FileSizeType;
Console.Write("What type do you want: ");
FileSizeType = Convert.ToInt32(Console.ReadLine());
return FileSizeType;
}
static string NomCategorie(int c)
{
//get FileSizeType into c
string FileType;
if (c == 1)
{
return FileType = "Small";
}
else if (c == 2)
{
return FileType = "Medium";
}
else if (c == 3)
{
return FileType = "Large";
}
else if (c == 4)
{
return FileType = "Huge";
}
else
{
return FileType="Non-valid choice";
}
I would suggest using enum class
public enum Test
{
Small = 1,
Medium = 2,
Large = 3,
Huge = 4
}
then you can simply convert the number by using
int integer = 1;
if (Enum.IsDefined(typeof(Test), integer)
{
Console.WriteLine((Test)integer).
}
else
{
Console.WriteLine("Bad Integer");
}
output:
Small
Looking at your existing code, you are already returning the value of FileSizeType from the ChoisirCategory, so you can capture it in a variable and then pass that to the NomCategorie method to get the category name, for example:
int categoryId = ChoisirCategory();
string categoryName = NomCategorie(categoryId);
Note that there are many other improvements that can be made (for example, what happens if the user types in "two" instead of "2"?), but I think those suggestions may be out of scope based on the question.
Here's how your code could be simplified if you combine both suggestions above. I've also added an if clause to verify that the value is not higher than those available.
static enum AvailableSizes
{
Small = 1,
Medium = 2,
Large = 3,
Huge = 4
}
static int ChoisirCategory()
{
int FileSizeType;
GetInput:
Console.Write("What type do you want: ");
FileSizeType = Convert.ToInt32(Console.ReadLine());
// Ensure the value is not higher than expected
// (you could also check that it is not below the minimum value)
if (FileSizeType > Enum.GetValues(typeof(AvailableSizes)).Cast<int>().Max());
{
Console.WriteLine("Value too high.");
goto GetInput;
}
return FileSizeType;
}
static string NomCategorie(int c)
{
if (Enum.IsDefined(typeof(AvailableSizes), c)
{
return (AvailableSizes)c;
}
else
{
return "Invalid category";
}
}
Then somewhere in your code you would call these using this statement
string categoryStr = NomCategorie(ChoisirCategory());
Console.WriteLinte(categoryStr); // or do whatever you want with the returned value
With this code, if the input is higher than 4, it will output "Value too high." and ask the question again until the value is no higher than 4.
If the user types 0 or a negative value, then it will output "Invalid category".
This might help you decide where you want to handle input errors: right after user input or during the parsing of the number to a string.

Is there an easier way to parse an int to a generic Flags enum?

I've got a generic function to parse an object into a generic Enum.
However, I'm running into an issue when trying to safely parse an int into a [Flags] Enum.
Directly using Enum.ToObject() works to parse valid combinations, but will just return the original value if there isn't a flag combination.
Additionally, when there's no explicit enum member for a combination of flags, Enum.ToName() and Enum.IsDefined() don't return helpful values.
For Example:
[Flags]
public enum Color
{
None = 0,
Red = 1,
Green = 2,
Blue = 4,
}
// Returns 20
Enum.ToObject(typeof(Color), 20)
// Returns ""
Enum.ToName(typeof(Color), 3)
// Returns false
Enum.IsDefined(typeof(Color), 3)
I've written a function that I think technically works, but it seems like there has to be a better way to do this.
My Function:
public static T ParseEnumerator<T>(object parseVal, T defaultVal) where T : struct, IConvertible
{
Type ttype = typeof(T);
if (!ttype.IsEnum)
{
throw new ArgumentException("T must be an enumerated type");
}
bool isFlag = ttype.GetCustomAttribute(typeof(FlagsAttribute)) != null;
try
{
if (parseVal == null)
{
return defaultVal;
}
else if (parseVal is T)
{
return (T)parseVal;
}
else if (parseVal is string)
{
return (T)Enum.Parse(ttype, parseVal.ToString(), true);
}
//**************** The section at issue **********************************/
else if (isFlag && parseVal is int)
{
List<string> flagsList = new List<string>();
int maxVal = 0;
//Loop through each bit flag
foreach (var val in Enum.GetValues(ttype))
{
if (CountBits((int)val) == 1)
{
if ((int)val > maxVal)
maxVal = (int)val;
// If the current bit is set, add the flag to the result
if (((int)parseVal & (int)val) == (int)val)
{
string enumName = Enum.GetName(ttype, val);
if (!string.IsNullOrEmpty(enumName))
flagsList.Add(enumName);
}
}
}
// Is the value being parsed over the highest bitwise value?
if ((int)parseVal >= (maxVal << 1))
return defaultVal;
else
return (T)Enum.Parse(ttype, string.Join(",", flagsList));
}
//************************************************************************/
else
{
string val = Enum.GetName(ttype, parseVal);
if (!string.IsNullOrEmpty(val))
return (T)Enum.ToObject(ttype, parseVal);
else
return defaultVal;
}
}
catch
{
return defaultVal;
}
}
Is there something I'm missing? Or is there another way to parse these values safely?
Any help is appreciated, thanks!
-MM
Since your generic function has to know the Enum type to begin with, you can just scrap the function and use basic casting instead.
using System;
namespace SO_58455415_enum_parsing {
[Flags]
public enum CheeseCharacteristics {
Yellow = 1,
Mouldy = 2,
Soft = 4,
Holed = 8
}
public static class Program {
static void Main(string[] args) {
CheeseCharacteristics cc = (CheeseCharacteristics)12;
Console.WriteLine(cc);
}
}
}
If all you want to know is if you have a value that can be created using the enum flags.. that's pretty easy, as long as we can assume that each flag is "sequential" (e.g. there are no gaps between the flags). All numbers between 1 and the sum of all flag values can be made by some combination of flags. You can simply sum the flag values together and compare that to your question value.
public static bool IsValidFlags<T>(int checkValue) where T:Enum {
int maxFlagValue = ((int[])Enum.GetValues(typeof(T))).Sum();
return (0 < checkValue) && (checkValue <= maxFlagValue);
}
For future reference, you can constrain your generic parameter to an enum:
void fx<T> () where T:Enum { }

How can I use if condition to add values

I want to obtain a total of three values:
when none of the values is null, the total calculates successfully but when one or more of the values is null,
it returns an error:
"input string was not in correct format"
Below is the code:
double TotalVar = double.Parse(ExScore.Tables[0].Rows[i]["CAT1"].ToString()) +
double.Parse(ExScore.Tables[0].Rows[i]["CAT2"].ToString()) +
double.Parse(ExScore.Tables[0].Rows[i]["CAT3"].ToString()) +
double.Parse(ExScore.Tables[0].Rows[i]["EXAM"].ToString());
Use local function like this to safely parse data :
double Parse(string data)
{
return double.TryParse(data, NumberStyles.AllowDecimalPoint,
CultureInfo.InvariantCulture, out double val) ? val : 0;
}
if(ExScore == null || ExScore.Tables[0] == null ||
IsNullOrEmpty(ExScore.Tables[0].Rows[i]["CAT1"]) ||
IsNullOrEmpty(ExScore.Tables[0].Rows[i]["CAT2"]) ||
IsNullOrEmpty(ExScore.Tables[0].Rows[i]["CAT3"]) ||
IsNullOrEmpty(ExScore.Tables[0].Rows[i]["EXAM"])
)
{
//throw exception
//set value by default
}
//calcul normally
public bool IsNullOrEmpty(object value)
{
return value == null || string.IsNullOrEmpty(value.ToString());
}
Create a console app and test the app below. Everything you need is in the code below. Note: It is bad to direct cast strings without a minimal error handling, also you can/will have problems with "." or "," decimal separators in these casts. - Different regions use different decimal separators, and this can impact the quality of your results.
class Program
{
static void Main(string[] args)
{
//SumValues(
//ExScore.Tables[0].Rows[i]["CAT2"].ToString(),
//ExScore.Tables[0].Rows[i]["CAT3"].ToString(),
//ExScore.Tables[0].Rows[i]["EXAM"].ToString()
//);
Console.WriteLine(SumValues("100,32", "100,08", "100,10",null));
Console.WriteLine(SumValues("abcsdf", "ba123", "100,10", null));
Console.WriteLine(SumValues("100,32", "100,08", "100,10", null));
Console.ReadLine();
}
//simple function that will try to sum any number of strings you pass
public static double SumValues(params string[] values)
{
double sum=0;
foreach (var item in values)
{
//if the string is null, empty or white space, proceed to the next iteration in the loop
if (string.IsNullOrWhiteSpace(item))
continue;
if (IsNumeric(item))
sum += double.Parse(item);
}
return sum;
}
//Minimal 'error handling' function to avoid exceptions parsing strings
public static bool IsNumeric(string s)
{
double output;
return double.TryParse(s, out output);
}
}

Nunit Negative Tests Expected Exception

I have a class that I check the index or the length of the string. And I want to write a Nunit Negative Test:
if the length of the string Out of Range the Nunit Test is true. Or the first index is digit "false" the Nunit Test is true.
What i try:
My CheckKeyClass:
public void SetKey(string keyToAnalyse)
{
Line = new string[keyToAnalyse.Length];
int nummeric;
bool num;
if (Line.Length == 0 || Line.Length < 24)
{
throw new Exception("Index Out of Range " + Line.Length);
}
// Ist Product a Character
num = int.TryParse(Line[0], out nummeric);
if (!num)
{
if (Line[0] == "K")
{
Product = 0;
}
}
else
{
throw new Exception("The Productnumber is not right: " + Line[0] ". \nPlease give a Character.");
}
}
My Nunit Test:
[Test]
public void NegativeTests()
{
keymanager.SetKey("KM6163-33583-01125-68785");
// Throws<ArgumentOutOfRangeException>(() => keymanager.Line[24]);
}
// ExpectedException Handling
public static void Throws<T>(Action func) where T : Exception
{
var exceptionThrown = false;
try
{
func.Invoke();
}
catch (T)
{
exceptionThrown = true;
}
if (!exceptionThrown)
{
throw new AssertFailedException(String.Format("An exception of type {0} was expected, but not thrown", typeof(T)));
}
}
So if the Line.length Out of Range the Test must be green also true.
How do I use that the Tests is true?
thx
Use Assert.Throws()
string keyHasLengthOf24 = "KM6163-33583-01125-68785";
var ex = Assert.Throws<Exception>(() => keymanager.SetKey(keyHasLengthOf24));
Assert.That(ex.Message, Is.EqualTo("Index Out of Range "));
See this SO answer for further detail.

Categories