Testing FluentValidation ChildRules - c#

Given the following object:
public class PatchDTO
{
public PatchDTO()
{
Data = new List<Datum>();
}
public List<Datum> Data { get; set; }
public class Datum
{
public Datum()
{
Attributes = new Dictionary<string, object>();
}
public string Id { get; set; }
public Dictionary<string, object> Attributes { get; set; }
}
}
I have my validator set as follows:
RuleFor(oo => oo.Data)
.NotEmpty()
.WithMessage("One or more Data blocks must be provided");
RuleForEach(d => d.Data).ChildRules(datum =>
{
datum.RuleFor(d => d.Id)
.NotEmpty()
.WithMessage("Invalid 'Data.Id' value");
});
Which I'm trying to test using the test extensions as such:
[Theory]
[InlineData(null)]
[InlineData("")]
public void Id_Failure(string id)
{
dto.Data[0].Id = id;
var result = validator.TestValidate(dto);
result.ShouldHaveValidationErrorFor(oo => oo.Data[0].Id)
.WithErrorMessage("Invalid 'Data.Id' value");
}
But when I run the test it says:
FluentValidation.TestHelper.ValidationTestException
HResult=0x80131500
Message=Expected a validation error for property Id
----
Properties with Validation Errors:
[0]: Data[0].Id
But as you can see under the 'Validation Errors', it has actually picked up in the validation failure but isn't tying it to this test. So how do I test these ChildRules or tell the test extension method which property it should actually be checking?
(I also used the validator.ShouldHaveValidationErrorFor directly with the same results)

I've had this problem before and resorted to using the string overload for ShouldHaveValidationErrorFor
The following (nunit) test passes
[TestCase(null)]
[TestCase("")]
public void Id_InvalidValue_HasError(string id)
{
var fixture = new Fixture();
var datum = fixture.Build<PatchDTO.Datum>().With(x => x.Id, id).Create();
var dto = fixture.Build<PatchDTO>().With(x => x.Data, new List<PatchDTO.Datum> { datum }).Create();
var validator = new PatchDTOValidator();
var validationResult = validator.TestValidate(dto);
validationResult.ShouldHaveValidationErrorFor("Data[0].Id")
.WithErrorMessage("Invalid 'Data.Id' value");
}
It's been a while since I looked at it, but I believe the issue is in the extension ShouldHaveValidationErrorFor matching on property name and the property expression overload doesn't resolve the property name to 'Data[0].Id'. If you inspect the validation results you'll get a ValidationError object that looks something like this
{
"PropertyName":"Data[0].Id",
"ErrorMessage":"Invalid 'Data.Id' value",
"AttemptedValue":"",
"CustomState":null,
"Severity":0,
"ErrorCode":"NotEmptyValidator",
"FormattedMessageArguments":[
],
"FormattedMessagePlaceholderValues":{
"PropertyName":"Id",
"PropertyValue":""
},
"ResourceName":null
}
EDIT:
Had a quick peek into the property expression overload, as per below
public IEnumerable<ValidationFailure> ShouldHaveValidationErrorFor<TProperty>(Expression<Func<T, TProperty>> memberAccessor)
{
return ValidationTestExtension.ShouldHaveValidationError(this.Errors, ValidatorOptions.PropertyNameResolver(typeof (T), memberAccessor.GetMember<T, TProperty>(), (LambdaExpression) memberAccessor), true);
}
Presumably you could use another/write your own property name resolver to handle the case as it is settable. You'd probably have to dig into the expression to do it.

Related

mock elastic search innerhits

I have read the document https://gist.github.com/netoisc/5d456850d79f246685fee23be2469155
which well know how to mock elasticsearch query
But I have a case which return result have innerhits
Documents = searchResult.Hits.Select(
h => {
if (h.InnerHits.TryGetValue("books", out var data)) {
ht.Source.Books = data!.Documents<book>();
}
ht.Source.Books = ht.Source.Books.Where(k=>k.country=="US");
return h.Source;
})
From my mock test
I have do this
var innerHitResult = new Mock<InnerHitsResult>();
innerHitResult.SetupGet(s => s.Documents<book>()).Returns(new List<book>());
var innerHitDictionary = new Dictionary<string, InnerHitsResult> {
{
"books", innerHitResult.Object
}
};
I've got the error on innerHitResult.SetupGet , which error say :
System.ArgumentException: Expression is not a property access: s => s.Documents<book>()
Even I use innerHitResult.Setup is not working
Can I know how to do mock for inner hit ?
In the other simple example
if I have a class :
public class BlogSearchResult
{
public InnerMetaData Hits { get; set; }
public IEnumerable<T> Document<T>() where T : class => Hits.Documents<T>();
}
I want to mock BlogSearchResult which want to do
var blogs = fixture.CreateMany<Blog>();
var blogSearch = new Mock<BlogSearchResult>();
blogSearch.SetupGet(s => s.Document<Blog>()).Returns(blogs);
or
blogSearch.Setup(s => s.Document<Blog>()).Returns(blogs);
They are all return error, is that possible ?

Unit test to check for unused properties

I have a function that runs through the properties of a class and replaces the keyword between two dollar signs with the same name from a template.
An example of a class:
public class FeedMessageData : IMailObject
{
public string Username { get; private set;}
public string SubscriptionID { get; private set; }
public string MessageTime { get; private set; }
public string Subject { get; private set; }
public FeedMessageData(string username, string subscriptionID, DateTime messageTime)
{
this.Username = username;
this.SubscriptionID = subscriptionID;
this.MessageTime = messageTime.ToShortDateString();
this.Subject = "Feed " + DateTime.Now + " - SubscriptionID: " + this.SubscriptionID;
}
}
And this is the function to replace the template with the properties:
private string mergeTemplate(string template, IMailObject mailObject)
{
Regex parser = new Regex(#"\$(?:(?<operation>[\w\-\,\.]+) ){0,1}(?<value>[\w\-\,\.]+)\$", RegexOptions.Compiled);
var matches = parser.Matches(template).Cast<Match>().Reverse();
foreach (var match in matches)
{
string operation = match.Groups["operation"].Value;
string value = match.Groups["value"].Value;
var propertyInfo = mailObject.GetType().GetProperty(value);
if (propertyInfo == null)
throw new TillitException(String.Format("Could not find '{0}' in object of type '{1}'.", value, mailObject));
object dataValue = propertyInfo.GetValue(mailObject, null);
template = template.Remove(match.Index, match.Length).Insert(match.Index, dataValue.ToString());
}
return template;
}
I'm looking to create a unit test that writes to the console, possible properties that aren't utilized in the template. An example would be if there wasn't a $SubscriptionID$ in the template. I've tried using PropertyInfo, which gives me the properties of the class, but how do I then use this information to check if they have already been used in the template?
Moq (https://github.com/moq/moq4/wiki) provides ways to verify property/method access.
Follow the tutorials on this link for more details. To verify that your properties are being consumed in your template, you can make use of the VerifyGet method, an example below:
[Fact]
public void VerifyAllPropertiesHaveBeenConsumedInTemplate()
{
var mockMailObject = new Mock<IMailObject>();
var template = "yourTemplateOrMethodThatReturnsYourTemplate";
var result = mergeTemplate(template, mockMailObject.Object);
mockMailObject.VerifyGet(m => m.Username, Times.Once);
mockMailObject.VerifyGet(m => m.SubscriptionID, Times.Once);
mockMailObject.VerifyGet(m => m.MessageTime, Times.Once);
mockMailObject.VerifyGet(m => m.Subject, Times.Once);
}

Fluent Validation changing CustomAsync to MustAsync

Could some one please help me to resolved this? i'm trying to change CustomAsync to MustAsync, but i couldn't make things to work. Below is my custom method
RuleFor(o => o).MustAsync(o => {
return CheckIdNumberAlreadyExist(o)
});
private static async Task<ValidationFailure> CheckIdNumberAlreadyExist(SaveProxyCommand command)
{
if (command.Id > 0)
return null;
using (IDbConnection connection = new SqlConnection(ConnectionSettings.LicensingConnectionString))
{
var param = new DynamicParameters();
param.Add("#idnumber", command.IdNumber);
var vehicle = await connection.QueryFirstOrDefaultAsync<dynamic>("new_checkDuplicateProxyIdNumber", param, commandType: CommandType.StoredProcedure);
return vehicle != null
? new ValidationFailure("IdNumber", "Id Number Already Exist")
: null;
}
}
To make it work with the latest version of the FluentValidation, I had to use the codes like below.
RuleFor(ws => ws).MustAsync((x, cancellation) => UserHasAccess(x)).WithMessage("User doesn't have access to perform this action");
Please notice the lambda expression here MustAsync((x, cancellation) => UserHasAccess(x)), without this I was always getting an error as cannot convert from 'method group' to 'Func<Worksheet, CancellationToken, Task<bool>>
Below is my custom UserHasAccess function.
private async Task <bool> UserHasAccess(Worksheet worksheet) {
var permissionObject = await _dataProviderService.GetItemAsync(worksheet.FileItemId);
if (permissionObject is null) return false;
if (EditAccess(permissionObject.Permission)) return true;
return false;
}
I'm assuming you're using a version of FluentValidation prior to version 6, as you're not passing in a Continuation Token, so I've based my answer on version 5.6.2.
Your example code does not compile, for starters, as you're missing a semi-colon in your actual rule. You are also evaluating two different properties on the SaveProxyCommand parameter.
I've built a very small POC based on some assumptions:
Given 2 classes:
public class SaveProxyCommand {
public int Id { get; set; }
}
public class ValidationFailure {
public string PropertyName { get; }
public string Message { get; }
public ValidationFailure(string propertyName, string message){
Message = message;
PropertyName = propertyName;
}
}
And a validator:
public class SaveProxyCommandValidator : AbstractValidator<SaveProxyCommand>{
public SaveProxyCommandValidator()
{
RuleFor(o => o).MustAsync(CheckIdNumberAlreadyExists)
.WithName("Id")
.WithState(o => new ValidationFailure(nameof(o.IdNumber), "Id Number Already Exist"));
}
private static async Task<bool> CheckIdNumberAlreadyExists(SaveProxyCommand command) {
if (command.Id > 0)
return true;
var existingIdNumbers = new[] {
1, 2, 3, 4
};
// This is a fudge, but you'd make your db call here
var isNewNumber = !(await Task.FromResult(existingIdNumbers.Contains(command.IdNumber)));
return isNewNumber;
}
}
I didn't include the call to the database, as that's not part of your problem. There are a couple of things of note here:
You're not setting the .WithName annotation method, but when you're setting up a validation rule for an object you have to do this, as FluentValidation expects you to specify specific properties to be validated by default, if you pass in an entire object it just doesn't know how to report errors back.
Must/MustAsync need to return a bool/Task<bool> instead of a custom object. To get around this, you can specify a custom state to be returned when failing validation.
You can then get access to this like this:
var sut = new SaveProxyCommand { Id = 0, IdNumber = 3 };
var validator = new SaveProxyCommandValidator();
var result = validator.ValidateAsync(sut).GetAwaiter().GetResult();
var ValidationFailures = result.Errors?.Select(s => s.CustomState).Cast<ValidationFailure>();
The above does not take into account empty collections, it's just an example of how to dig into the object graph to retrieve custom state.
As a suggestion, fluentvalidation works best if you set up individual rules per property, instead of validating the entire object. My take on this would be something like this:
public class SaveProxyCommandValidator : AbstractValidator<SaveProxyCommand>{
public SaveProxyCommandValidator()
{
RuleFor(o => o.IdNumber).MustAsync(CheckIdNumberAlreadyExists)
.Unless(o => o.Id > 0)
.WithState(o => new ValidationFailure(nameof(o.IdNumber), "Id Number Already Exist"));
}
private static async Task<bool> CheckIdNumberAlreadyExists(int numberToEvaluate) {
var existingIdNumbers = new[] {
1, 2, 3, 4
};
// This is a fudge, but you'd make your db call here
var isNewNumber = !(await Task.FromResult(existingIdNumbers.Contains(numberToEvaluate)));
return isNewNumber;
}
}
This read more like a narrative, it uses the .Unless construct to only run the rule if Id is not more than 0, and does not require the evaluation of the entire object.

Using [JsonProperty("name")] in ModelState.Errors

We have a couple of models that override the name via JsonProperty, but this causes an issue when we get validation errors through ModelState. For example:
class MyModel
{
[JsonProperty("id")]
[Required]
public string MyModelId {get;set;}
}
class MyModelController
{
public IHttpActionResult Post([FromBody] MyModel model)
{
if (!ModelState.IsValid)
{
return HttpBadRequest(ModelState);
}
/* etc... */
}
}
The above Post will return the error The MyModelId field is required. which isn't accurate. We'd like this to say The id field is required.. We've attempted using [DataMember(Name="id")] but get the same result.
Question 1: Is there a way we can get ModelState errors to show the JSON property name rather than the C# property name aside from providing our own error messages on every [Required] attribute?
-- Update --
I've been playing around with this and found a "do-it-yourself" method for re-creating the error messages using custom property names. I'm really hoping there's a built-in way to do this, but this seems to do the job...
https://gist.github.com/Blackbaud-JasonTremper/b64dc6ddb460afa1698daa6d075857e4
Question 2: Can ModelState.Key be assumed to match the <parameterName>.<reflectedProperty> syntax or are there cases where this might not be true?
Question 3: Is there an easier way to determine what the JSON parameter name is expected to be rather than searching via reflection on [DataMember] or [JsonProperty] attributes?
Did you try using DisplayName attribute?
displayname attribute vs display attribute
Also, you can assign an error message to [Required] attribute.
[Required(ErrorMessage = "Name is required")]
I also faced this problem, I modified some code from your link to fit my WebAPI. modelState will also store the old key which is the variable name of the model, plus the Json Property names.
First, create the filter ValidateModelStateFilter
Add [ValidateModelStateFilter] above controller method
The filter source code:
public class ValidateModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var descriptor = actionContext.ActionDescriptor;
var modelState = actionContext.ModelState;
if (descriptor != null)
{
var parameters = descriptor.GetParameters();
var subParameterIssues = modelState.Keys
.Where(s => s.Contains("."))
.Where(s => modelState[s].Errors.Any())
.GroupBy(s => s.Substring(0, s.IndexOf('.')))
.ToDictionary(g => g.Key, g => g.ToArray());
foreach (var parameter in parameters)
{
var argument = actionContext.ActionArguments[parameter.ParameterName];
if (subParameterIssues.ContainsKey(parameter.ParameterName))
{
var subProperties = subParameterIssues[parameter.ParameterName];
foreach (var subProperty in subProperties)
{
var propName = subProperty.Substring(subProperty.IndexOf('.') + 1);
var property = parameter.ParameterType.GetProperty(propName);
var validationAttributes = property.GetCustomAttributes(typeof(ValidationAttribute), true);
var value = property.GetValue(argument);
modelState[subProperty].Errors.Clear();
foreach (var validationAttribute in validationAttributes)
{
var attr = (ValidationAttribute)validationAttribute;
if (!attr.IsValid(value))
{
var parameterName = GetParameterName(property);
// modelState.AddModelError(subProperty, attr.FormatErrorMessage(parameterName));
modelState.AddModelError(parameterName, attr.FormatErrorMessage(parameterName));
}
}
}
}
}
}
}
private string GetParameterName(PropertyInfo property)
{
var dataMemberAttribute = property.GetCustomAttributes<DataMemberAttribute>().FirstOrDefault();
if (dataMemberAttribute?.Name != null)
{
return dataMemberAttribute.Name;
}
var jsonProperty = property.GetCustomAttributes<JsonPropertyAttribute>().FirstOrDefault();
if (jsonProperty?.PropertyName != null)
{
return jsonProperty.PropertyName;
}
return property.Name;
}
}
You can access the parameter type, get the json name, and then replace the property name with the json name. Something like this:
var invalidParameters = (from m in actionContext.ModelState
where m.Value.Errors.Count > 0
select new InvalidParameter
{
ParameterName = m.Key,
ConstraintViolations = (from msg in m.Value.Errors select msg.ErrorMessage).ToArray()
}).AsEnumerable().ToArray();
if (actionContext.ActionDescriptor.Parameters.Count == 1)
{
var nameMapper = new Dictionary<string, string>();
foreach (var property in actionContext.ActionDescriptor.Parameters[0].ParameterType.GetProperties())
{
object[] attributes = property.GetCustomAttributes(typeof(JsonPropertyNameAttribute), false);
if (attributes.Length != 1) continue;
nameMapper.Add(property.Name, ((JsonPropertyNameAttribute) attributes[0]).Name);
}
var modifiedInvalidParameters = new List<InvalidParameter>();
foreach (var invalidParameter in invalidParameters)
{
if(invalidParameter.ParameterName != null && nameMapper.TryGetValue(invalidParameter.ParameterName, out var mappedName))
{
var modifiedConstraintViolations = new List<string>();
foreach (var constraintViolation in invalidParameter.ConstraintViolations ?? Enumerable.Empty<string>())
{
modifiedConstraintViolations.Add(constraintViolation.Replace(invalidParameter.ParameterName, mappedName));
}
modifiedInvalidParameters.Add(new InvalidParameter
{
ParameterName = mappedName,
ConstraintViolations = modifiedConstraintViolations.ToArray()
});
}
else
{
modifiedInvalidParameters.Add(invalidParameter);
}
}
invalidParameters = modifiedInvalidParameters.ToArray();
}
public struct InvalidParameter
{
[JsonPropertyName("parameter_name")]
public string? ParameterName { get; set; }
[JsonPropertyName("constraint_violations")]
public string[]? ConstraintViolations { get; set; }
}

Why are no query parameters being passed to my NancyFX module?

I am running a self-hosted NancyFX web server inside of my application. Right now I have one module hosted:
public class MetricsModule : NancyModule
{
private IStorageEngine _storageEngine;
public MetricsModule(IStorageEngine storageEngine) : base("/metrics")
{
_storageEngine = storageEngine;
Get["/list"] = parameters =>
{
var metrics = _storageEngine.GetKnownMetrics();
return Response.AsJson(metrics.ToArray());
};
Get["/query"] = parameters =>
{
var rawStart = parameters.start;
var rawEnd = parameters.end;
var metrics = parameters.metrics;
return Response.AsJson(0);
};
}
}
My Bootstrapper class is:
public class OverlookBootStrapper : DefaultNancyBootstrapper
{
private readonly IStorageEngine _storageEngine;
public OverlookBootStrapper(IStorageEngine storageEngine)
{
_storageEngine = storageEngine;
}
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
container.Register(_storageEngine);
}
}
I am trying to test it with the following test:
[TestInitialize]
public void Init()
{
_storageEngine = new Mock<IStorageEngine>();
var bootstrapper = new OverlookBootStrapper(_storageEngine.Object);
_browser = new Browser(bootstrapper);
}
[TestMethod]
public void Query_Builds_Correct_Query_From_Parameters()
{
var metric = new Metric("device", "category", "name", "suffix");
var startDate = DateTime.Now;
var endDate = DateTime.Now.AddMinutes(10);
var path = "/metrics/query";
var response = _browser.Get(path, with =>
{
with.HttpRequest();
with.Query("start", startDate.ToString());
with.Query("end", endDate.ToString());
with.Query("metrics", metric.ToParsableString());
});
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Incorrect status code returned");
_storageEngine.Verify(x => x.ExecuteQuery(It.Is<Query>(y => y.StartDate == startDate)), Times.Once());
_storageEngine.Verify(x => x.ExecuteQuery(It.Is<Query>(y => y.EndDate == endDate)), Times.Once());
_storageEngine.Verify(x => x.ExecuteQuery(It.Is<Query>(y => y.Metrics.Contains(metric))), Times.Once());
}
When this test is debugged and a breakpoint is put on return Response.AsJson(0);, I inspected the parameters object and noticed that parameters.Count is zero, and all 3 values are null.
What am I doing incorrectly?
Edit: When I bring up this endpoint in the web browser, the same issue occurs. I get a result of 0 sent back to my browser, but when debugging I see that no query string parameters I specify have been recognized by NancyFX.
The parameters argument to your lambda contains the route parameters you captured in the in your Get["/query"]. In this case nothing. See #thecodejunkie's comment for an example where there is something.
To get to the query paramters use Request.Query. That's also a dynamic and will contain whatever query parameters was in the request. Like so:
Get["/query"] = parameters =>
{
var rawStart = Request.Query.start;
var rawEnd = Request.Query.end;
var metrics = Request.Query.metrics;
return Response.AsJson(0);
};
This should work with your tests too.
You can let NancyFx's model binding take care of the url query string.
public class RequestObject
{
public string Start { get; set; }
public string End { get; set; }
public string Metrics { get; set; }
}
/query?start=2015-09-27&end=2015-10-27&metrics=loadtime
Get["/query"] = x =>
{
var request = this.Bind<RequestObject>();
}

Categories

Resources