I've been updating my fluent Validations from 10 to 11 but I've suddenly got a lot of unit test errors that push me toward believing that Fluent Validations is not executing all of the rules. This seems against all the documentation and I'm not finding anyone else complaining about it so I'm confused.
I have a test that looks for several null values, and should return a failure for each, however it only returns the first failure, swapping the rule order makes the error return differently.
Any tips are appreciated.
public sealed class Validator : AbstractValidator<Command>
{
public Validator()
{
//ClassLevelCascadeMode = CascadeMode.Continue;
RuleFor(x => x.Id).ValidateId();
RuleFor(x => x.Name).ValidateAccountName();
RuleFor(x => x.Description).ValidateAccountDescription();
RuleFor(x => x.Status).ValidateAccountStatus();
When(
x => !(x.prop1is null) || !(x.prop2 is null) || !(x.prop 3 is null),
() =>
{
RuleFor(x => x.prop1Enum).NotNull().IsInEnum();
RuleFor(x => x.prop2Number).ValidateNumber();
RuleFor(x => x.prop3Number).ValidateNumber();
}
);
}
}
Test
public void Validator_ShouldReturnErrors_WhenAccountInfoIsPartiallyProvided(
bool excludeProp1,
bool excludeProp2,
bool excludeProp3,
UpdateAccount.Validator sut,
IFixture fixture
)
{
// arrange
var command = BuildValidCommand(fixture, excludeProp1, excludeProp2, excludeprop3)
.Create();
// act
var result = sut.Validate(command);
// assert
using var _ = new AssertionScope();
result.Errors.Should().HaveCountLessOrEqualTo(3);
if (excludeprop1)
{
result.Errors.Should()
.Contain(
f => f.PropertyName == nameof(UpdateAccount.Command.prop1) &&
f.ErrorMessage.Contains("must not be empty")
);
}
if (excludeProp2)
{
result.Errors.Should()
.Contain(
f => f.PropertyName == nameof(UpdateAccount.Command.Prop2) &&
f.ErrorMessage.Contains("must not be empty")
);
}
if (excludeProp3)
{
result.Errors.Should()
.Contain(
f => f.PropertyName == nameof(UpdateAccount.Command.Prop3) &&
f.ErrorMessage.Contains("must not be empty")
);
}
}
I figured out that the data generators for my tests was putting the validators in this bad state. I have fixed them all by putting them into the proper state.
Related
At the moment, in my controller's service method GetSubAccounts(accountId), I have this:
Account account = await context.Accounts.SingleOrDefaultAsync(x => x.Id == accountId);
IQueryable<Account> subAccounts = context.Accounts.Include(x => x.AccountCodes).AsNoTracking();
return await mapper.ProjectTo<SubAccountViewModel>(subAccounts, null, x => x.SubAccounts)
.Where(x => x.PersonId == account.PersonId && x.AccountId != null).ToListAsync();
My SubAccountViewModel is as follows: (note that it has a collection of itself)
public class SubAccountViewModel : Base.AccountViewModel
{
public virtual ICollection<AccountCodeViewModel> AccountCodes { get; set; }
public virtual ICollection<SubAccountViewModel> SubAccounts { get; set; }
}
My mapping profile is:
internal class SubAccountMappingProfile : Profile
{
public SubAccountMappingProfile()
{
CreateMap<Account, SubAccountViewModel>()
.ForMember(x => x.AccountCodes, options => options.ExplicitExpansion())
.ForMember(x => x.SubAccounts, options => options.ExplicitExpansion())
.ReverseMap();
}
}
And this is the JSON I'm getting as a result:
[
{
"id":"c236718f-9d91-4eea-91ee-66760a716343",
"personId":"06d3857d-6a49-4e1c-b63c-7edc83d30cbd",
"accountId":null,
"username":"test same person",
"email":"testsameperson#gmail.com",
"birthDate":"2021-01-02",
"subaccounts":null
}
]
The problem:
I'm getting a top-level array of subaccounts for the accountId parameter I pass to the method. Fine. (There's just one, but nevermind that.)
What I do want is the main account at top-level, with the array of subaccounts as part of it.
I.e.
{
"id":"f61fedc2-eb60-4ba9-9d17-8d41b9cae9f1",
"personId":"06d3857d-6a49-4e1c-b63c-7edc83d30cbd",
"accountId":"f61fedc2-eb60-4ba9-9d17-8d41b9cae9f1",
"username":"test person",
"email":"testperson#gmail.com",
"birthDate":"2021-01-01",
"subaccounts":[
{
"id":"c236718f-9d91-4eea-91ee-66760a716343",
"personId":"06d3857d-6a49-4e1c-b63c-7edc83d30cbd",
"accountId":"f61fedc2-eb60-4ba9-9d17-8d41b9cae9f1",
"username":"test same person",
"email":"testsameperson#gmail.com",
"birthDate":"2021-01-02",
"subaccounts":null
}
]
}
How do I do it?
The problem was one of logic.
To start with, my service method (and my API controller) was returning Task<IEnumerable<SubAccountViewModel>>, when it should return Task<SubAccountViewModel>.
Then my solution was:
Account account = await context.Accounts.SingleOrDefaultAsync(x => x.Id == accountId);
IQueryable<Account> accounts = context.Accounts.AsNoTracking();
SubAccountViewModel subAccountViewModel = await mapper.ProjectTo<SubAccountViewModel>(accounts, null, x => x.AccountCodes)
.SingleOrDefaultAsync(x => x.Id == accountId);
subAccountViewModel.SubAccounts = await mapper.ProjectTo<SubAccountViewModel>(accounts, null, x => x.AccountCodes, x => x.SubAccounts)
.Where(x => x.PersonId == account.PersonId && x.AccountId != null).ToListAsync();
return subAccountViewModel;
This returns the resultset I wanted.
I need to transform my property (remove white space on a string) first before applying validation.
Specifically I want to check if a string is part of a enum but the string may contain whitespaces (which enums don't allow far as I know)
Something like...
RuleFor(x => x.Value).IsEnumName(typeof(EnumType))
(x.Value should have whitespaces removed first)
The FluentValidation method Transform is designed for this case. The following uses the basic white space remover from Everson's answer:
RuleFor(x => x.Value)
.Transform(x => x.Replace(" ", string.Empty))
.IsEnumName(typeof(EnumType));
I'd opt to go with a stronger white space remover that catches tabs etc
RuleFor(x => x.Value)
.Transform(x => new string(x.Where(c => !Char.IsWhiteSpace(c)).ToArray()))
.IsEnumName(typeof(EnumType));
No need for a custom validator (e.g., Must) and personally I'd avoid writing one unless there was no other way.
I'd pop that white space remover into an extension (MVP, you should handle the null case; my preference would be a null guard but that's another topic):
public static class StringExtensions
{
public static string RemoveWhiteSpace(this string target){
return new string(target.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
}
Then the rule is a lot more readable:
RuleFor(x => x.Value)
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType))
.When(x => x != null);
Something to be aware of: I found that if Transform returned null the IsEnumName rule will pass. Personally I don't like that so I'd include a When rule builder option to only test when Value is provided, or a not empty rule to ensure it is provided.
Working LINQPad sample:
public enum EnumType
{
Value1,
Value2,
Value3
}
public class Foo
{
public Guid Id { get; set; }
public string Value { get; set; }
}
public class FooValidator : AbstractValidator<Foo>
{
public FooValidator()
{
RuleFor(x => x.Value)
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType));
.When(x => x != null);
}
}
public static class StringExtensions
{
public static string RemoveWhiteSpace(this string target)
{
return new string(target.Where(c => !Char.IsWhiteSpace(c)).ToArray());
}
}
void Main()
{
var validator = new FooValidator();
var foo1 = new Foo { Id = Guid.NewGuid(), Value = "Value 1" };
var result1 = validator.Validate(foo1);
Console.WriteLine(result1.IsValid);
var foo2 = new Foo { Id = Guid.NewGuid(), Value = "Value2" };
var result2 = validator.Validate(foo2);
Console.WriteLine(result2.IsValid);
var foo3 = new Foo { Id = Guid.NewGuid(), Value = "Value 3" };
var result3 = validator.Validate(foo3);
Console.WriteLine(result3.IsValid);
var foo4 = new Foo { Id = Guid.NewGuid(), Value = "This is not a valid enum value" };
var result4 = validator.Validate(foo4);
Console.WriteLine(result4.IsValid);
var foo5 = new Foo { Id = Guid.NewGuid(), Value = null };
var result5 = validator.Validate(foo5);
Console.WriteLine(result5.IsValid);
}
EDIT:
As per your additional comment about wrapping all of this into an extension:
public static class FluentValidationExtensions
{
public static IRuleBuilderOptions<T, string> IsEnumTypeMember<T>(this IRuleBuilderInitial<T, string> target)
{
return target
.Transform(x => x?.RemoveWhiteSpace())
.IsEnumName(typeof(EnumType))
.When(x => x != null);
}
}
Then update the rule to use it:
RuleFor(x => x.Value).IsEnumTypeMember();
This is just an MVP, I don't really know all of your use cases; you may want to make it more generic so you could apply it to other enums.
You can use a custom method
RuleFor(x => x.Valor).Must(BeEnumType);
...
private bool BeEnumType(string v)
{
return Enum.IsDefined(typeof(EnumType), v.Replace(" ", string.Empty));
}
I'm testing a PUT with two string:
company.CurrencyCode = request.CurrencyCode ?? company.CurrencyCode;
company.CountryIso2 = request.Country ?? company.CountryIso2;
and I tried with a rule like:
public UpdateCompanyValidator()
{
RuleSet(ApplyTo.Put, () =>
{
RuleFor(r => r.CountryIso2)
.Length(2)
.When(x => !x.Equals(null));
RuleFor(r => r.CurrencyCode)
.Length(3)
.When(x => !x.Equals(null));
});
}
as I don't mind to get a null on those properties, but I would like to test the Length when the property is not a null.
What is the best way to apply rules when a property is nullable and we just want to test if it's not null?
One of the ways would be:
public class ModelValidation : AbstractValidator<Model>
{
public ModelValidation()
{
RuleFor(x => x.Country).Must(x => x == null || x.Length >= 2);
}
}
I prefer the following syntax:
When(m => m.CountryIso2 != null,
() => {
RuleFor(m => m.CountryIso2)
.Length(2);
);
Best syntax for me:
RuleFor(t => t.DocumentName)
.NotEmpty()
.WithMessage("message")
.DependentRules(() =>
{
RuleFor(d2 => d2.DocumentName).MaximumLength(200)
.WithMessage(string.Format(stringLocalizer[""message""], 200));
});
I have the following code to validate an entity:
public class AffiliateValidator : AbstractValidator<Affiliate>
{
public AffiliateValidator ()
{
RuleFor(x => x.IBAN).SetValidator(new ValidIBAN()).Unless( x => String.IsNullOrWhiteSpace(x.IBAN));
}
}
And ValidIBAN() Code:
public class ValidIBAN : PropertyValidator
{
public ValidIBAN()
:base("IBAN \"{PropertyValue}\" not valid.")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var iban = context.PropertyValue as string;
IBAN.IBANResult result = IBAN.CheckIban(iban, false);
return result.Validation == (IBAN.ValidationResult.IsValid);
}
}
}
So,CheckIBAN method of IBAN class does the dirty job.
Now, what I need to to is apply the following rule for another property:
If DirectDebit (bool) is true, then IBAN can't be empty and also it must be valid.
I can do this:
RuleFor(x => x.DirectDebit).Equal(false).When(a => string.IsNullOrEmpty(a.IBAN)).WithMessage("TheMessage.");
But how can I Invoke another rule, IBAN's rule in this case, in order to check if is or not valid?
Often the problems are simpler than they seem. This is the solution I adopt to aply the rule for DirectDebit field.
RuleFor(x => x.DirectDebit).Must(HaveValidAccounts).When(x => x.DirectDebit)
.WithMessage("TheMessage");
and change the rule for IBAN also:
RuleFor(x => x.IBAN).Must(IsValidIBAN)
.Unless(x => String.IsNullOrWhiteSpace(x.IBAN))
.WithMessage("The IBAN \"{PropertyValue}\" is not valid.");
...and then:
private bool HaveValidAccounts(ViewModel instance, bool DirectDebit)
{
if (!DirectDebit)
{ return true; }
bool CCCResult = IsValidCCC(instance.CCC);
bool IBANResult = IsValidIBAN(instance.IBAN);
return CCCResult || IBANResult;
}
private bool IsValidIBAN(string iban)
{
return CommonInfrastructure.Finantial.IBAN.CheckIban(iban, false).Validation == IBAN.ValidationResult.IsValid;
}
the trick is use instance parameter of Must() to do whetever I want.
I am using nhibernate 3 and fluent nhibernate
I have this enum
[Flags]
public enum Permission
{
View = 1,
Add = 2,
Edit = 4,
Delete = 8,
All = View | Add | Edit | Delete
}
Now say I want to find all users that have "view" or "all".
How could I do this with nhibernate(linq)?
session.Query<TableA>().Where x.Permission == Permission.View); // does not bring back all
session.Query<TableA>().Where x.Permission.HasFlag(Permission.View); // crashes
Is there away to do this with having to go
session.Query<TableA>().Where x.Permission == Permission.View || x.Permission == Permission.All);
Edit
public class TableAMap : ClassMap<TableA>
{
public TableAMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Permission).Not.Nullable().CustomType<PermissionTypes>();
}
}
If you have the following mapping for your TableA type:
public class TableAMap : ClassMap<TableA>
{
public TableAMap()
{
...
Map(x => x.Permission).CustomType(typeof(Permission)).Not.Nullable();
...
}
}
Then you should be able to issue the following query in your repository or wherever you do your data ccess:
public IList<TableA> GetTableByPermission(Permission permission)
{
return Session.Query<TableA>
.Where(x => x.Permission == permission ||
x.Permission == Permission.All)
.ToList();
}
Or you can check whether a single TableA instance has got the required permission:
public bool HasPermission(Guid guid, Permission permission)
{
var tableA = Session.Query<TableA>.SingleOrDefault(x => x.Id == guid);
if (tableA != null)
return (tableA.Permission & permission) == permission;
// Return false or whatever is appropriate.
return false;
}
You cannot use your HasFlag() method in the query, NHibernate does not understand how to use that in the query.