I am using .NET Core with Boilerplate. I'm trying to unit test some new forms that require that I have nested objects with properties. The Integration Tests use AbpAspNetCoreIntegratedTestBase<Startup>
which uses an instance of both HttpClient
and TestServer
. The client has various types of methods at its disposal. There are GetAsync, PostAsync, SendSync and PutAsync methods just to name a few.
I thought I had gotten comfortable with some of the methods and helper methods in this frame work and have been successful thus far. However, I have a form with an Model called Vendor, the Vendor has an Address Model as part of the view model. This is so I can reuse the Address View Model with other items in the application that also require Address(es).
One of the helpers that is used with BoilerPlate is GetUrl<TController>(string actionName, object queryStringParamsAsAnonymousObject)
Since this is a Post from a form I'm attempting to use public Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
No matter what I'm attempting to do, I'm getting a 400 Bad Request response and my test fails before it even gets inside the controller method. I'm at a loss of how to handle this.
Here are my Models:
VendorViewModel:
[AutoMap(typeof(Domains.Vendor))]
public class VendorViewModel : BaseViewModelEntity
{
[Required]
public string Name { get; set; }
[Required]
public string PointOfContact { get; set; }
[Required]
public string Email { get; set; }
[Required]
public int AddressId { get; set; }
//[Required]
//public string Address1 { get; set; }
//public string Address2 { get; set; }
//public string Address3 { get; set; }
//[Required]
//public string City { get; set; }
//[Required]
//public int State { get; set; }
//[Required]
//public string Zip { get; set; }
//[Required]
//public string Phone { get; set; }
//public string Fax { get; set; }
public AddressViewModel VendorAddress { get; set; }
public VendorViewModel()
{
VendorAddress = new AddressViewModel();
}
public VendorViewModel(VendorDto vendor)
{
Id = vendor.Id;
Name = vendor.Name;
IsActive = vendor.IsActive;
PointOfContact = vendor.PointOfContact;
Email = vendor.Email;
AddressId = vendor.AddressId;
CreatorUserId = vendor.CreatorUserId;
CreationTime = vendor.CreationTime;
DeleterUserId = vendor.DeleterUserId;
DeletionTime = vendor.DeletionTime;
LastModificationTime = vendor.LastModificationTime;
LastModifierUserId = vendor.LastModifierUserId;
//Address1 = vendor.Address.Address1;
//Address2 = vendor.Address.Address2;
//Address3 = vendor.Address.Address3;
//City = vendor.Address.City;
//State = vendor.Address.State;
//Zip = vendor.Address.Zip;
//Phone = vendor.Address.Phone;
//Fax = municipalities.Address.Fax;
VendorAddress = new AddressViewModel()
{
Id = vendor.Address.Id,
Address1 = vendor.Address.Address1,
Address2 = vendor.Address.Address2,
Address3 = vendor.Address.Address3,
City = vendor.Address.City,
State = vendor.Address.State,
Zip = vendor.Address.Zip,
Phone = vendor.Address.Phone,
Fax = vendor.Address.Fax,
CreationTime = vendor.Address.CreationTime,
};
}
}
Address View Model:
public class AddressViewModel : BaseViewModelEntity
{
[Required]
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
[Required]
public string City { get; set; }
[Required]
public int State { get; set; }
[Required]
public string Zip { get; set; }
[Required]
public string Phone { get; set; }
public string Fax { get; set; }
public AddressViewModel()
{
}
public AddressViewModel(AddressDto address)
{
Id = address.Id;
Address1 = address.Address1;
Address2 = address.Address2;
Address3 = address.Address3;
City = address.City;
State = address.State;
Zip = address.Zip;
Phone = address.Phone;
Fax = address.Phone;
CreatorUserId = address.CreatorUserId;
CreationTime = address.CreationTime;
DeleterUserId = address.DeleterUserId;
DeletionTime = address.DeletionTime;
LastModificationTime = address.LastModificationTime;
LastModifierUserId = address.LastModifierUserId;
}
}
I have my Test set up with xUnit
//Arrange
//Add Client Headers so User Auth and Permission Checkers work correctly
Client.DefaultRequestHeaders.Add("my-name", "admin");
Client.DefaultRequestHeaders.Add("my-id", "2");
//set up test data
var addressViewModel = new AddressViewModel()
{
Address1 = "123 This Way", City = "Arlington", State = 44, Zip = "76001", Phone = "8175555555",
CreationTime = DateTime.Now
};
var viewModelSave = new VenderViewModel()
{
Name = "Controller Test Name",
PointOfContact = "Tom Jerry",
Email = "Tom.Jerry@yolo.com",
CreationTime = DateTime.Now,
LastModificationTime = null,
IsActive = true,
AddressId = 0,
VendorAddress = addressViewModel
//Address1 = "123 This Way",
//City = "Arlington",
//State = 44,
//Zip = "76001",
//Phone = "8175555555"
};
/* This is an attempt to use string interpolation to create querystring parameters */
//var rawData =
// $"?Name={viewModelSave.Name}&Id={viewModelSave.Id}&PointOfContat=${viewModelSave.PointOfContact}&Email={viewModelSave.Email}&CreationTime={DateTime.Now}" +
// $"&LastModificationTime=&IsActive={viewModelSave.IsActive}&AddressId={viewModelSave.AddressId}&VendorAddress.Id={viewModelSave.VendorAddress.Id}&VendorAddress.Address1={viewModelSave.VendorAddress.Address1}" +
// $"&VendorAddress.City={viewModelSave.VendorAddress.City}&VendorAddress.State={viewModelSave.VendorAddress.State}&VendorAddress.Zip={viewModelSave.VendorAddress.Zip}&VendorAddress.Phone={viewModelSave.VendorAddress.Phone}" +
// $"&VendorAddress.CreationTime={DateTime.Now}&VendorAddress.IsActive={viewModelSave.VendorAddress.IsActive}";
/*This is an attempt to create a json object that could be serialize into an object as the "queryStringParamsAsAnonymousObject" that can be used in the GetUrl Helper method below */
var rawData = $"{{'Name':'Controller Test Name','PointOfContact':'Tom Jerry', 'Email': 'Tom.Jerry@yolo.com',"
+ "'CreationTime':'" + DateTime.Now + "','LastModificationTime':'','IsActive' : 'true','AddressId':'0','Address.Address1':'123 This Way',"
+ "'Address.City':'Arlington','Address.State':'44','Address.Zip':'76001','Address.Phone':'8175555556','Address.IsActive':'true'}";
var jsonData = JsonConvert.DeserializeObject(rawData);
//Serialize ViewModel to send with Post as part of the HttpContent object
var data = JsonConvert.SerializeObject(viewModelSave);
var vendorAddress = new
{
viewModelSave.vendorAddress.Id,
viewModelSave.vendorAddress.Address1,
viewModelSave.vendorAddress.City,
viewModelSave.vendorAddress.State,
viewModelSave.vendorAddress.Zip,
viewModelSave.vendorAddress.Phone,
viewModelSave.vendorAddress.IsActive,
viewModelSave.vendorAddress.CreationTime
};
//actually get the url from helper method (with various attempts at creating an anonymousObject directly
var url = GetUrl<VendorController>(nameof(VendorController.SaveVendor),
new
{
viewModelSave.Id,
viewModelSave.Name,
viewModelSave.PointOfContact,
viewModelSave.Email,
viewModelSave.CreationTime,
viewModelSave.LastModificationTime,
viewModelSave.IsActive,
viewModelSave.AddressId,
vendorAddress
//VendorAddress_Address1 = vendorAddress.Address1,
//VendorAddress_Id = vendorAddress.Id,
//VendorAddress_City = vendorAddress.City,
//VendorAddress_State = vendorAddress.State,
//VendorAddress_Zip = vendorAddress.Zip,
//VendorAddress_Phone = vendorAddress.Phone,
//VendorAddress_IsActive = vendorAddress.IsActive,
//VendorAddress = new
//{
// viewModelSave.VendorAddress.Id,
// viewModelSave.VendorAddress.Address1,
// viewModelSave.VendorAddress.City,
// viewModelSave.VendorAddress.State,
// viewModelSave.VendorAddress.Zip,
// viewModelSave.VendorAddress.Phone,
// viewModelSave.VendorAddress.IsActive,
// viewModelSave.VendorAddress.CreationTime
//},
//viewModelSave.Address1,
//viewModelSave.City,
//viewModelSave.State,
//viewModelSave.Zip,
//viewModelSave.Phone
}
);
var content = new StringContent(data, Encoding.UTF8, "application/x-www-form-urlencoded");
var message = new HttpRequestMessage {
Content = content,
Method = HttpMethod.Post,
RequestUri = new Uri("http://localHost" + url)
};
//Act
var response = await PostResponseAsObjectAsync<AjaxResponse>(url, content);
//Assert
var count = UsingDbContext(context => { return context.Municipalities.Count(x => x.IsActive); });
response.ShouldBeOfType<AjaxResponse>();
response.Result.ShouldNotBeNull();
count.ShouldBe(3);
}
As I attempt to debug what is happening. I've noticed that the VendorAddress properties that are sent via the test request do not match what the actual form post looks like when parsed in Chrome developer tools. In Chrome I see (example: PointOfContact:"Tom Jerry" IsActive:True VendorAddress.Address1:"123 This Way" VendorAddress.City: "Arlington") I cannot get my test data into that same format, therefore its not binding correctly to my view models on post, and thus returns a 400 response and fails the test.
I have gotten it to work if I remove the Address View Model all together and put those properties as properties of the VendorViewModel. However, I would run into the same if not similar issue if I'm attempting to save a collection of objects along with the main view model. I feel like there has to be a way to submit test form data via integration tests with boilerplate. I just need some missing piece to this puzzle.
The Solution that is working for both Post and Send Requests: .NET Core has a QueryHelper class that will take a dictionary and a uri string and convert it into a url with querystring parameters. QueryHelpers.AddQueryString(string uri, IDictionary queryString) This is part of Microsoft.AspNetCore.WebUtilities. Using this method I was able to properly prepare my formData. For a Post Request the Test would look like this
//Arrange
//Add Client Headers so User Auth and Permission Checkers work correctly
Client.DefaultRequestHeaders.Add("my-name", "admin");
Client.DefaultRequestHeaders.Add("my-id", "2");
//set up test data
var rawData = new Dictionary<string, string>(){{ "Id", "0"}, {"Name", "Controller Test Name"}, {"PointOfContact", "Tom Jerry"}
, {"Email", "Tom.Jerry@yolo.comy"}, {"CreationTime", $"{DateTime.Now}"}, {"LastModificationTime", ""}, {"IsActive", "true"}, {"AddressId", "0"}
, {"VendorAddress.Id", "0"}, {"VendorAddress.Address1", "123 This Way"}, {"VendorAddress.City", "Arlington"}, {"VendorAddress.State", "44"}, {"VendorAddress.Zip", "76001"}
, {"VendorAddress.Phone", "8175555555"}, {"VendorAddress.Fax", ""}, {"VendorAddress.IsActive", "true"}, {"VendorAddress.CreationTime", $"{DateTime.Now}"}, {"VendorAddress.LastModificationTime", ""}
};
var jsonData = JsonConvert.DeserializeObject(rawData);
//Serialize ViewModel to send with Post as part of the HttpContent object
var data = JsonConvert.SerializeObject(viewModelSave);
//actually get the url from helper method
var url = GetUrl<VendorController>(nameof(VendorController.SaveVendor));
var content = new StringContent(data, Encoding.UTF8, "application/x-www-form-urlencoded");
//Act
var response = await PostResponseAsObjectAsync<AjaxResponse>(url, content);
//Assert
var count = UsingDbContext(context => { return context.Municipalities.Count(x => x.IsActive); });
response.ShouldBeOfType<AjaxResponse>();
response.Result.ShouldNotBeNull();
count.ShouldBe(3);
Send Request Test would look like this
//Arrange
//Add Client Headers so User Auth and Permission Checkers work correctly
Client.DefaultRequestHeaders.Add("my-name", "admin");
Client.DefaultRequestHeaders.Add("my-id", "2");
//set up test data
var rawData = new Dictionary<string, string>(){{ "Id", "0"}, {"Name", "Controller Test Name"}, {"PointOfContact", "Tom Jerry"}
, {"Email", "Tom.Jerry@yolo.comy"}, {"CreationTime", $"{DateTime.Now}"}, {"LastModificationTime", ""}, {"IsActive", "true"}, {"AddressId", "0"}
, {"VendorAddress.Id", "0"}, {"VendorAddress.Address1", "123 This Way"}, {"VendorAddress.City", "Arlington"}, {"VendorAddress.State", "44"}, {"VendorAddress.Zip", "76001"}
, {"VendorAddress.Phone", "8175555555"}, {"VendorAddress.Fax", ""}, {"VendorAddress.IsActive", "true"}, {"VendorAddress.CreationTime", $"{DateTime.Now}"}, {"VendorAddress.LastModificationTime", ""}
};
var jsonData = JsonConvert.DeserializeObject(rawData);
//Serialize ViewModel to send with Post as part of the HttpContent object
var data = JsonConvert.SerializeObject(viewModelSave);
//actually get the url from helper method
var url = GetUrl<VendorController>(nameof(VendorController.SaveVendor));
var content = new StringContent(data, Encoding.UTF8, "application/x-www-form-urlencoded");
var message = new HttpRequestMessage {
Content = content,
Method = HttpMethod.Post,
RequestUri = new Uri("http://localHost" + url)
};
//Act
var response = await SendResponseAsObjectAsync<AjaxResponse>(message);
//Assert
var count = UsingDbContext(context => { return context.Municipalities.Count(x => x.IsActive); });
response.ShouldBeOfType<AjaxResponse>();
response.Result.ShouldNotBeNull();
count.ShouldBe(3);