Nancy.Testing exception thrown when testing POST route with Json content - nancy

I'm attempting to write tests for my Nancy module, and it's not going too well so far.
Attempting to test two separate actions, one GET which is working just fine and a POST with application/json content in the request.
I'm slightly stabbing in the dark as to how the test for the POST should be written, but have come up with the following:
using NUnit.Framework;
[TestFixture]
public class MyModuleTests
{
Browser browser;
[SetUp]
public void SetUp()
{
browser = new Browser(with =>
{
with.Module<MyModule>();
with.EnableAutoRegistration();
});
}
[Test]
public void Can_Get_View()
{
// When
var result = browser.Get("/View", with => with.HttpRequest());
// Then
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); // this works
}
[Test]
public void Can_Post_to_EvaluateDocument()
{
var testData = new List<FakeInputValue>(){new FakeInputValue()
{
Name = "a",
Value = 0.35
}};
// When
var result = browser.Post("/Evaluate", with => //the exception is thrown on this line
{
with.HttpRequest();
with.JsonBody<IList<FakeInputValue>>(testData);
});
// Then
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
}
}
class FakeInputValue
{
public string Name { get; set; }
public double Value { get; set; }
}
Unfortunately this throws the following juicy error
1) Test Error : MyModuleTests.Can_Post_to_EvaluateDocument
System.Exception : ConfigurableBootstrapper Exception
----> Nancy.RequestExecutionException : Oh noes!
----> System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
----> System.MissingMethodException : No parameterless constructor defined for this object.
at Nancy.Testing.PassThroughStatusCodeHandler.Handle(HttpStatusCode statusCode, NancyContext context)
at Nancy.NancyEngine.CheckStatusCodeHandler(NancyContext context)
at Nancy.NancyEngine.<>c__DisplayClass8.<HandleRequestInternal>b__6(Task`1 completeTask)
at Nancy.NancyEngine.HandleRequestInternal(Request request, Func`2 preRequest)
at Nancy.NancyEngine.HandleRequest(Request request, Func`2 preRequest)
at Nancy.Testing.Browser.HandleRequest(String method, String path, Action`1 browserContext)
at MyModuleTests.Can_Post_to_EvaluateDocument() in line 49
--RequestExecutionException
at Nancy.NancyEngine.InvokeOnErrorHook(NancyContext context, ErrorPipeline pipeline, Exception ex)
--TargetInvocationException
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Nancy.ModelBinding.DefaultBodyDeserializers.JsonBodyDeserializer.Deserialize(String contentType, Stream bodyStream, BindingContext context)
at Nancy.ModelBinding.DefaultBinder.Bind(NancyContext context, Type modelType, Object instance, BindingConfig configuration, String[] blackList)
at Nancy.ModelBinding.DynamicModelBinderAdapter.TryConvert(ConvertBinder binder, Object& result)
at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at MyModule.<.ctor>b__9(Object parameters)
at CallSite.Target(Closure , CallSite , Func`2 , Object )
at Nancy.Routing.Route.<>c__DisplayClass4.<Wrap>b__3(Object parameters, CancellationToken context)
--MissingMethodException
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at Nancy.Json.JavaScriptSerializer.ConvertToObject(IDictionary`2 dict, Type type)
at Nancy.Json.JavaScriptSerializer.ConvertToList(ArrayList col, Type type)
at Nancy.Json.JavaScriptSerializer.ConvertToType[T](Object obj)
Any help as to (a) how I might better test POST routes with application/json content in Nancy or (b) resolve this particular error!
edit 1
My module is as follows:
public class MyModule : NancyModule
{
public MyModule()
{
Get["/View"] = parameters =>
{
return View["myView"];
};
Post["/Evaluate"] = parameters =>
{
this.inputs = this.Bind<List<InputValue>>();
var evaluator = new Evaluator(inputs);
return Response.AsJson<List<Evaluation>>(evaluator.Evaluate());
};
}
}

After a lot of debugging and frustrated outputting to Console, I've tracked down the cause of the bug. The issue was with the lack of public setters in my InputValue class.
The buggy class was written as so:
public class InputValue
{
private readonly string _name;
private readonly double _value;
public InputValue(string inputName, double inputValue)
{
this._name = inputName;
this._value = inputValue;
}
public string Name
{
get
{
return this._name;
}
}
public double Value
{
get
{
return this._value;
}
}
}
I resolved the issue quite simply by allowing the properties to be set, as so:
public class InputValue
{
public string Name
{
get;
set;
}
public double Value
{
get;
set;
}
}

Related

How to get AbpEntityChanges data for each row of the entity table?

my application needs to show the entity change log for each row of the product table by clicking on a button in the row. But I don't know how to get the auditing data from the abpEntityChanges table.
the DTO I need for the frontend:
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
namespace App.Products
{
public class EntityChangeDto : EntityDto<Guid> {
public Guid AuditLogId {get; set;}
public string EntityId {get; set;}
public EntityChangeType ChangeType {get;set;}
public DateTime ChangeTime {get; set;}
public string UserName {get; set;}
}
}
So I tried to make the IEntityChangeRepository first (derived from EntityChange class of the auditing module), and hoped to be able to use GetQueryableAsync in the AppService.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.AuditLogging;
namespace App.Products
{
public interface IEntityChangeRepository : IRepository<EntityChange,Guid>
{
// need anything else here?
}
}
an then added new task GetEntityChangeListAsync in my ProductAppService (of course also IProductAppService).
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.AuditLogging;
namespace App.Products
{
public class ProductAppService :
CrudAppService<Product,ProductDto,Guid,GetProductListDto,CreateUpdateProductDto>,
IProductAppService
{
private readonly IEntityChangeRepository _entityChangeRepository;
private readonly IAuditLogRepository _auditLogRepository;
public ProductAppService(
IRepository<Product, Guid> repository
IEntityChangeRepository entityChangeRepository,
IAuditLogRepository auditLogRepository
)
: base(repository)
{
_entityChangeRepository = entityChangeRepository;
_auditLogRepository = auditLogRepository;
}
public async Task<ListResultDto<EntityChangeDto>> GetEntityChangeListAsync(string id) {
var queryable = await _entityChangeRepository.GetQueryableAsync();
queryable = queryable
.Where(p => p.EntityId.Equals(id))
.OrderBy(p => p.ChangeTime);
var query = from entityChange in queryable
join auditLog in _auditLogRepository on entityChange.AuditLogId equals auditLog.Id
select new { entityChange, auditLog };
var queryResult = await AsyncExecuter.ToListAsync(query);
var entityChangeDtos = queryResult.Select(x =>
{
var entityChangeDto = ObjectMapper.Map<EntityChange,EntityChangeDto>(x.entityChange);
entityChangeDto.UserName = x.auditLog.UserName;
return entityChangeDto;
}).ToList();
return new ListResultDto<EntityChangeDto>(
entityChangeDtos
);
}
}
}
But I keep getting error code 500. And I think the problem is most likely with the IEntityChangeRepository. Missing some mapping or the Repository is not queryable this way?
Could someone help please? many thanks ^^ I am beginner in the coding world but did my research in abp documents, couldnt solve it though :)
Error log
2021-03-25 07:59:20.505 +01:00 [ERR] An exception was thrown while activating Castle.Proxies.ProductAppServiceProxy.
Autofac.Core.DependencyResolutionException: An exception was thrown while activating Castle.Proxies.ProductAppServiceProxy.
---> Autofac.Core.DependencyResolutionException: None of the constructors found with 'Volo.Abp.Autofac.AbpAutofacConstructorFinder' on type 'Castle.Proxies.ProductAppServiceProxy' can be invoked with the available services and parameters:
Cannot resolve parameter 'App.Products.IEntityChangeRepository entityChangeRepository' of constructor 'Void .ctor(Castle.DynamicProxy.IInterceptor[], Volo.Abp.Domain.Repositories.IRepository`2[App.Products.Product,System.Guid], App.Products.IEntityChangeRepository, Volo.Abp.AuditLogging.IAuditLogRepository)'.
at Autofac.Core.Activators.Reflection.ReflectionActivator.GetAllBindings(ConstructorBinder[] availableConstructors, IComponentContext context, IEnumerable`1 parameters)
at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
at Autofac.Core.Activators.Reflection.ReflectionActivator.<ConfigurePipeline>b__11_0(ResolveRequestContext ctxt, Action`1 next)
at Autofac.Core.Resolving.Middleware.DisposalTrackingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
at Autofac.Builder.RegistrationBuilder`3.<>c__DisplayClass41_0.<PropertiesAutowired>b__0(ResolveRequestContext ctxt, Action`1 next)
at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
--- End of inner exception stack trace ---
at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
at Autofac.Builder.RegistrationBuilder`3.<>c__DisplayClass35_0.<OnPreparing>b__0(ResolveRequestContext ctxt, Action`1 next)
at Autofac.Core.Resolving.Middleware.CoreEventMiddleware.Execute(ResolveRequestContext context, Action`1 next)
at Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
at Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMiddleware.Execute(ResolveRequestContext context, Action`1 next)
at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
at Autofac.Core.Resolving.ResolveOperation.ExecuteOperation(ResolveRequest request)
at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
at Microsoft.AspNetCore.Mvc.Controllers.ServiceBasedControllerActivator.Create(ControllerContext actionContext)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
2021-03-25 07:59:20.507 +01:00 [ERR] ---------- Exception Data ----------
ActivatorChain = Castle.Proxies.ProductAppServiceProxy
This will help you to find your way;
public class EfCoreAuditLogRepository : EfCoreRepository<IAuditLoggingDbContext, AuditLog, Guid>, IAuditLogRepository
{
public EfCoreAuditLogRepository(IDbContextProvider<IAuditLoggingDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public override async Task<IQueryable<AuditLog>> WithDetailsAsync()
{
return (await GetQueryableAsync()).IncludeDetails();
}
public virtual async Task<EntityChange> GetEntityChange(
Guid entityChangeId,
CancellationToken cancellationToken = default)
{
var entityChange = await (await GetDbContextAsync()).Set<EntityChange>()
.AsNoTracking()
.IncludeDetails()
.Where(x => x.Id == entityChangeId)
.OrderBy(x => x.Id)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
if (entityChange == null)
{
throw new EntityNotFoundException(typeof(EntityChange));
}
return entityChange;
}
public virtual async Task<List<EntityChange>> GetEntityChangeListAsync(
string sorting = null,
int maxResultCount = 50,
int skipCount = 0,
Guid? auditLogId = null,
DateTime? startTime = null,
DateTime? endTime = null,
EntityChangeType? changeType = null,
string entityId = null,
string entityTypeFullName = null,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName, includeDetails);
return await query.OrderBy(sorting.IsNullOrWhiteSpace() ? (nameof(EntityChange.ChangeTime) + " DESC") : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<long> GetEntityChangeCountAsync(
Guid? auditLogId = null,
DateTime? startTime = null,
DateTime? endTime = null,
EntityChangeType? changeType = null,
string entityId = null,
string entityTypeFullName = null,
CancellationToken cancellationToken = default)
{
var query = await GetEntityChangeListQueryAsync(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName);
var totalCount = await query.LongCountAsync(GetCancellationToken(cancellationToken));
return totalCount;
}
public virtual async Task<EntityChangeWithUsername> GetEntityChangeWithUsernameAsync(
Guid entityChangeId,
CancellationToken cancellationToken = default)
{
var auditLog = await (await GetDbSetAsync()).AsNoTracking().IncludeDetails()
.Where(x => x.EntityChanges.Any(y => y.Id == entityChangeId)).FirstAsync(GetCancellationToken(cancellationToken));
return new EntityChangeWithUsername()
{
EntityChange = auditLog.EntityChanges.First(x => x.Id == entityChangeId),
UserName = auditLog.UserName
};
}
protected virtual async Task<IQueryable<EntityChange>> GetEntityChangeListQueryAsync(
Guid? auditLogId = null,
DateTime? startTime = null,
DateTime? endTime = null,
EntityChangeType? changeType = null,
string entityId = null,
string entityTypeFullName = null,
bool includeDetails = false)
{
return (await GetDbContextAsync())
.Set<EntityChange>()
.AsNoTracking()
.IncludeDetails(includeDetails)
.WhereIf(auditLogId.HasValue, e => e.AuditLogId == auditLogId)
.WhereIf(startTime.HasValue, e => e.ChangeTime >= startTime)
.WhereIf(endTime.HasValue, e => e.ChangeTime <= endTime)
.WhereIf(changeType.HasValue, e => e.ChangeType == changeType)
.WhereIf(!string.IsNullOrWhiteSpace(entityId), e => e.EntityId == entityId)
.WhereIf(!string.IsNullOrWhiteSpace(entityTypeFullName), e => e.EntityTypeFullName.Contains(entityTypeFullName));
}
}

Why is Nancy model binding failing?

I have a Nancy.SelfHost service that is working just fine for the Get routes, but is failing on the Post route. If fails on the Bind<T>() command in the following route with a "No parameterless constructor defined for this object" error:
Post["/schedule"] = _ =>
{
var schedule = this.Bind<OatiDay[]>();
PostSchedule(schedule);
return HttpStatusCode.Created;
};
Here is the call stack returned with the error:
Nancy.RequestExecutionException: Oh noes! ---> System.MissingMethodException: No parameterless constructor defined for this object.
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at Nancy.Serialization.JsonNet.JsonNetBodyDeserializer.CreateObjectWithBlacklistExcluded(BindingContext context, Object deserializedObject)
at Nancy.Serialization.JsonNet.JsonNetBodyDeserializer.Deserialize(String contentType, Stream bodyStream, BindingContext context)
at Nancy.ModelBinding.DefaultBinder.DeserializeRequestBody(BindingContext context)
at Nancy.ModelBinding.DefaultBinder.Bind(NancyContext context, Type modelType, Object instance, BindingConfig configuration, String[] blackList)
at Nancy.ModelBinding.DynamicModelBinderAdapter.TryConvert(ConvertBinder binder, Object& result)
at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at Nancy.ModelBinding.ModuleExtensions.Bind[TModel](INancyModule module)
at TSID.Scada.Nancy.ScheduleService.<.ctor>b__2(Object _) in p:\TSIDDev\TSID.Scada\TSID.Scada.Nancy\ScheduleService.cs:line 24
at CallSite.Target(Closure , CallSite , Func`2 , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at Nancy.Routing.Route.<>c__DisplayClass4.<Wrap>b__3(Object parameters, CancellationToken context)
--- End of inner exception stack trace ---
at Nancy.NancyEngine.InvokeOnErrorHook(NancyContext context, ErrorPipeline pipeline, Exception ex)
It looks as though something is going wrong with the deserializer, but I can't tell what is being created. The post code is passing all its tests on the server using the Nancy Browser, so something must be wrong with the request. But I can't figure out what it is. The OatiDay class is just a poco object.
public class OatiDay
{
[BsonId]
public string Id { get; set; }
[BsonDateTimeOptions(DateOnly = true, Kind = DateTimeKind.Local)]
public DateTime Date { get; set; }
public String Facility { get; set; }
public Production OnPeak { get; set; }
public Production OffPeak { get; set; }
public class Production
{
public Int32 ScheduleForDay { get; set; }
public double? ActualForDay { get; set; }
public Int32 ScheduledToDate { get; set; }
public double? ActualToDate { get; set; }
}
}
Here is the request sent from an Angular $http service:
POST http://buckhorn1:7000/schedule HTTP/1.1
Host: buckhorn1:7000
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:39.0) Gecko/20100101 Firefox/39.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Referer: http://buckhorn1:7203/schedule
Content-Length: 793
Origin: http://buckhorn1:7203
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
[{"id":"20150712","date":"2015-07-12T00:00:00Z","facility":"Watson","onPeak":{"scheduleForDay":0,"actualForDay":6.56,"scheduledToDate":77,"actualToDate":92.61},"offPeak":{"scheduleForDay":12,"actualForDay":4.26,"scheduledToDate":71,"actualToDate":64.97},"hasPeak":false},{"id":"20150713","date":"2015-07-13T00:00:00Z","facility":"Watson","onPeak":{"scheduleForDay":8,"actualForDay":8,"scheduledToDate":85,"actualToDate":100.61},"offPeak":{"scheduleForDay":4,"actualForDay":4,"scheduledToDate":75,"actualToDate":68.97},"hasPeak":true},{"id":"","date":"2015-07-14T00:00:00.000Z","facility":"Watson","hasPeak":true,"onPeak":{"scheduleForDay":9,"scheduledToDate":94,"actualForDay":9,"actualToDate":109.61},"offPeak":{"scheduleForDay":5,"scheduledToDate":80,"actualForDay":5,"actualToDate":73.97}}]
Can anyone tell me how to troubleshoot this?
I could not get the Nancy model-binding to work. I suspected it had something to do with the deserialization of the camel cased body. I replaced this:
var schedule = this.Bind<OatiDay[]>();
with this:
var reader = new StreamReader(this.Request.Body);
string text = reader.ReadToEnd();
var schedule = JsonConvert.DeserializeObject<OatiDay[]>(text);
and my service is working.
Had the same problem. This is actually Newtonsoft.Json problem.
One of these can help:
1) Changing OatiDay[] to List. This actually was my solution.
2) Add wrapper class with property if possible { OatiDay[] OatiDays { get; set; } }
3) Direct Json.Net Deserialization (pthalacker answer)
4) Implement custom model binder Nancy.ModelBinding.IModelBinder.

How to implement INotifyDataErrorInfo for Datarow

i have a datarow class that implements Dynamicobject and INotifyPropertyChanged and INotifyDataErrorInfo
Also have a property called 'GridData'(datarows) in this class that is bound to xaml for displaying in the grid
May i know how to implement public IEnumerable GetErrors(string propertyName)
correctly since 'GridData' property can have many property bags
Thanks
//This is your row... more or less.
public class GridData : DynamicObject, INotifyDataErrorInfo
{
private Dictionary<string, object> _propertyValues = new Dictionary<string, object>();
//this object holds your errors.
private Dictionary<string, List<ValidationResult>> _errorsContainer = new Dictionary<string, List<ValidationResult>>();
//when this fires it notifies the UI the errors of this object have changed.
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
//This tells the UI there are errors.
public bool HasErrors
{
get { return this._errorsContainer.Count > 0; }
}
//this allows the UI to retrieve all errors for a given property
public IEnumerable GetErrors(string propertyName)
{
return this._errorsContainer[propertyName];
}
//This sets the error for a given property and fires the errors changed event
protected void SetError(string propertyName, IEnumerable<ValidationResult> errors)
{
List<ValidationResult> existingErrors;
if(this._errorsContainer.TryGetValue(propertyName, out existingErrors) != true)
{
this._errorsContainer[propertyName] = errors.ToList();
}
else
{
existingErrors.AddRange(errors);
}
this.RaiseErrorsChanged(propertyName);
}
//This clears the errors for a given property
protected void ClearErrors(string propertyName)
{
this._errorsContainer.Remove(propertyName);
this.RaiseErrorsChanged(propertyName);
}
//This raises the event that the errors of this object have changed.
protected void RaiseErrorsChanged(string propertyName)
{
if(this.ErrorsChanged != null)
{
this.ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
//inherited from dynamic object this returns the value for a given property.
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
//this gives you the name of the property.
string name = binder.Name;
return _propertyValues.TryGetValue(name, out result);
}
//inherited from dynamic object, this is called when a property is set.
public override bool TrySetMember(SetMemberBinder binder, object value)
{
string propertyName = binder.Name;
List<ValidationResult> validationErrors = new List<ValidationResult>();
//store the value in the propertyValues regardless if it is erroneous.
_propertyValues[propertyName] = value;
//this is where you test the value of the property.
if(value /* whatever condition you use to test it */)
{
//no errors so update the ui.
this.ClearErrors(propertyName);
}
else
{
//there was an error for this value.
ValidationResult result = new ValidationResult("The value is wrong.");
//add the error to the list of errors for this property
validationErrors.Add(result);
//update the error container telling it there are errors for this property.
//fire the errors changed event, and update ui.
this.SetError(propertyName, validationErrors);
}
return true;
}
}

Is there a better way to test this MVVM Light Message that doesn't cause an occasional threading error?

I have a property in my viewmodel whose sole job is to send out a message when the property is changed. I wanted to unit test that it was indeed doing it's job so I wrote the following test
[TestFixture, RequiresSTA]
public class ToolBarViewModelTests
{
private SecondaryScalingMode _SecondaryScalingMode;
private void RecordSecondaryScalingSetting(SecondaryScalingMode obj)
{
_SecondaryScalingMode = obj;
}
[Test]
public void AssertCombineAllCornersScalingMessageIsSent()
{
var vm = CreateNewToolBar();
_SecondaryScalingMode = SecondaryScalingMode.IndividualCorner;
AppMessages.SetSecondaryScalingMode.Register(this, (Action<SecondaryScalingMode>)RecordSecondaryScalingSetting);
vm.CombineAllCornersScaling = true;
Assert.IsFalse(vm.IndividualCornerScaling);
Assert.IsFalse(vm.LeftRightScaling);
Assert.IsFalse(vm.FrontRearScaling);
Assert.IsTrue(vm.CombineAllCornersScaling);
Assert.AreEqual(SecondaryScalingMode.CombineAllCorners, _SecondaryScalingMode);
}
}
This test asserts the correct behavior, but it occassionally fails both locally and on my build server due to a threading error. The error is as follows
Test(s) failed. System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
----> System.InvalidOperationException : The calling thread cannot access this object because a different thread owns it.
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
Is there a better way I can write this test that would prevent it from failing randomly in this way?
OK this is the approach I usually take. This is pretty close to what you do.
Keep in Mind you can also highjack the Messenger and subscribe to the Messenger In the unit test.
I posted a simplified version of your code to match to example.
I used to have also some occansionly strange errors in my unit tests and it was a timing/thread issue...
for that it helped to reset the Messenger foreach Unit test...
This is the viewmodel (simplefied guess work =) ... ):
public class UnitTestViewModel:ViewModelBase
{
public UnitTestViewModel()
{
Messenger.Default.Register<string>(this, "SetSecondaryScalingMode", (message) =>
{
UpdateScalingMode();
});
}
private bool _CombineAllCornersScaling;
public bool CombineAllCornersScaling
{
get {
return _CombineAllCornersScaling;
}
set {
_CombineAllCornersScaling = value;
Messenger.Default.Send<string>("update", "SetSecondaryScalingMode");
RaisePropertyChanged("CombineAllCornersScaling");
}
}
private bool _IndividualCornerScaling;
public bool IndividualCornerScaling
{
get
{
return _IndividualCornerScaling;
}
set
{
_IndividualCornerScaling = value;
RaisePropertyChanged("IndividualCornerScaling");
}
}
private bool _LeftRightScaling;
public bool LeftRightScaling
{
get
{
return _LeftRightScaling;
}
set
{
_LeftRightScaling = value;
RaisePropertyChanged("LeftRightScaling");
}
}
private bool _FrontRearScaling;
public bool FrontRearScaling
{
get
{
return _FrontRearScaling;
}
set
{
_FrontRearScaling = value;
RaisePropertyChanged("FrontRearScaling");
}
}
private void UpdateScalingMode()
{
IndividualCornerScaling = false;
LeftRightScaling = false;
FrontRearScaling = true;
}
}
this is the Unit test (using MSTest but could apply to other packages)...
[TestMethod]
public void UpdaetScalingMode()
{
Messenger.Reset();
var vm = new UnitTestViewModel();
bool updateMessageWasSend = false;
string _thisCanBeTheObjectYouWerePassing = String.Empty;
Messenger.Default.Register<string>(vm, "SetSecondaryScalingMode", (msg) =>
{
_thisCanBeTheObjectYouWerePassing = msg;
updateMessageWasSend = true;
});
vm.CombineAllCornersScaling = true;
Assert.AreEqual(true, updateMessageWasSend);
Assert.AreEqual(false, vm.IndividualCornerScaling);
Assert.AreEqual(false, vm.LeftRightScaling);
Assert.AreEqual(true, vm.CombineAllCornersScaling);
Assert.AreEqual("update", _thisCanBeTheObjectYouWerePassing);
}
Rember this is simplyfied to show the concept.
I did just passed a string for message payload but this can be as complex as you want with all messenger functionalities....
I hope this helps... and otherwise it would be helpful to see the ViewModel code to cum up with another test strategy ...
P.S.: I also developed the habit to have 1 assertion per Test only and write more tests instead...

WCF Dictionary<string, object> serialization

I have a service that has a method like this
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService
{
[OperationContract]
public object MyMethod(string param1, string param2, object[] myarray)
{
//do stuff
return result;
}
}
I call my method from my code like this:
public Dictionary<string, object> MyDictionary{ get; set; }
serv.MyMethodCompleted += new EventHandler<MyServiceReference.MyMethodCompletedEventArgs>(serv_MyMethodCompleted);
serv.MyMethodAsync("param1","param2", new ObservableCollection<object>(){MyDictionary});
void serv_MyMethodCompleted(object sender, MyServiceReference.MyMethodCompletedEventArgs e)
{
//Happy happy joy joy
}
Everithing craches with this error:
There was an error while trying to
serialize parameter :myarray. The
InnerException message was 'Type
'System.Collections.Generic.Dictionary`2[[System.String,
mscorlib, Version=2.0.5.0,
Culture=neutral,
PublicKeyToken=7cec85d7bea7798e],[System.Object,
mscorlib, Version=2.0.5.0,
Culture=neutral,
PublicKeyToken=7cec85d7bea7798e]]'
with data contract name
'ArrayOfKeyValueOfstringanyType:http://schemas.microsoft.com/2003/10/Serialization/Arrays'
is not expected. Add any types not
known statically to the list of known
types - for example, by using the
KnownTypeAttribute attribute or by
adding them to the list of known types
passed to DataContractSerializer.'.
Please see InnerException for more
details.
public System.IAsyncResult BeginCallMethod(string param1, string param2, System.Collections.ObjectModel.ObservableCollection<object> myarray, System.AsyncCallback callback, object asyncState) {
object[] _args = new object[3];
_args[0] = param1;
_args[1] = param2;
_args[2] = myarray;
System.IAsyncResult _result = base.BeginInvoke("MyMethod", _args, callback, asyncState); <--here it craches
return _result;
}
what did I did wrong? how can I fix this?
The myArray parameter and the return value needs to be a strongly typed and attributed with DataContract and DataMember attributes. The myArray should be collection like IEnumerable<Item> that can be serialized:
[DataContract]
class Item
{
[DataMember]
public string Name {get;set;}
[DataMember]
public double Cost {get;set;}
}

Resources