I return IActionResult with the value of an anonymous object from a controller method.
How do I get the data out again?
Here is the controller method reduced to the very problem:
[HttpPost]
public async Task<IActionResult> UploadFile(IFormFile file)
{
long size = file.Length;
return Ok(new { count = 1, size });
}
Then I get the .Value property, which is an object and make it dynamic.
But no, it is not recognised.
[Fact]
public async Task UploadFileTest()
{
// # Given.
var formFile = new Mock<IFormFile>();
var sut = new HomeController(null);
// # When.
IActionResult result = await sut.UploadFile(formFile.Object);
// # Then.
OkObjectResult okResult = (OkObjectResult)result; // Ok.
dynamic dynValue = okResult.Value; // Ok.
var count = dynValue.count; // Not ok.
}
Expected result:
count should be 1.
Actual result:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : 'object' does not contain a definition for 'count'
Try reflection:
var count = (int)okResult.Value.GetType().GetProperty("count").GetValue(okResult.Value);
If your test and original code not in the same project, If that so, you need to create AssemblyInfo.cs in your original code project to make it (original) share the dynamic object structure.
you could see more at https://sodocumentation.net/csharp/topic/4264/assemblyinfo-cs-examples#-internalsvisibleto-
It shall has code like below (Replaced the string as your test name)
[assembly: InternalsVisibleTo("MyAssembly.UnitTests")]
Related
I have the following method under test:
public HomeController(IUserIpAddressHelper userIpAddressHelper)
{
_userIpAddressHelper = userIpAddressHelper;
}
[HttpGet]
public ActionResult Index()
{
var userIpAddress = _userIpAddressHelper.GetUserIpAddress(System.Web.HttpContext.Current);
if (_userIpAddressHelper.IsIpAddressOddOrEven(userIpAddress))
{
return RedirectToAction(HomePage);
}
return RedirectToAction(HomePageAlternative);
}
and I am testing as follows:
public void Test()
{
var userIpAddressHelper = Substitute.For<IUserIpAddressHelper>();
userIpAddressHelper.GetUserIpAddress(Arg.Any<HttpContext>()).Returns("0.0.0.2");
var controller = new HomeController(userIpAddressHelper);
var result = controller.Index();
Assert.IsInstanceOf<RedirectToRouteResult>(result);
var redirectToRouteResult = result as RedirectToRouteResult;
Assert.AreEqual(HomeController.HomePage, redirectToRouteResult.RouteValues["action"]);
}
However the test is failing due to the value of userIpAddress being "" an empty string, instead of 0.0.0.2 as I've set it. Can anyone please point out where I've gone wrong here?
Is userIpAddress definitely ""? It looks like the Returns in your original test is specified well, but if IUserIpAddressHelper is an interface then the substitute for it will not have a result stubbed for IsIpAddressOddOrEven, so it will always return false even if GetUserIpAddress is stubbed to return "0.0.0.2".
To get the test to mirror how the production code passes through the data, you can stub out both members:
var userIpAddressHelper = Substitute.For<IUserIpAddressHelper>();
userIpAddressHelper.GetUserIpAddress(Arg.Any<HttpContext>()).Returns("0.0.0.2");
userIpAddressHelper.IsIpAddressOddOrEven("0.0.0.2").Returns(true);
This will test that the production code correctly passes through the result of GetUserIpAddress to IsIpAddressOddOrEven.
Note: we could also stub these to work with "ip-address-result" and it would still work. We don't need a valid odd/even result returned, as we are not using a real implementation of IUserIpAddressHelper, just a substitute for testing. If you find it necessary to substitute for IUserIpAddressHelper in lots of tests and you want it to act like a real implementation (i.e. it will actually return whether an address is odd or even), it might be easier to write a TestUserIpAddressHelper.
Another way to avoid having the dependency between the results of GetUserIpAddress and IsIpAddressOddOrEven is to change the interface to have a bool IsIpAddressOddOrEven(HttpContext context) method that combines both operations. That way you would only need to stub one for the test.
If you have problems with System.Web.HttpContext.Current, you can try to mock IsIpAddressOddOrEven method instead. They will both do the same job for your test.
Like this:
public void Test()
{
var userIpAddressHelper = Substitute.For<IUserIpAddressHelper>();
userIpAddressHelper.IsIpAddressOddOrEven(Arg.Any<string>()).Returns(true);
var controller = new HomeController(userIpAddressHelper);
var result = controller.Index();
Assert.IsInstanceOf<RedirectToRouteResult>(result);
var redirectToRouteResult = result as RedirectToRouteResult;
Assert.AreEqual(HomeController.HomePage, redirectToRouteResult.RouteValues["action"]);
}
I use the following function which is all well and fine but i basically do the same operation about 20 times. For various end points of an api I am hitting how would one make this routing more Generic in the ability to pass and return type OF T.
public async Task<List<StockItem>> GetStockDataFromSage()
{
StockItem stockitems = new StockItem();
string content = "";
List<StockItem> result = new List<StockItem>();
var uri = new Uri(string.Format(Constants.GetStockItems, string.Empty));
var response = await _client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
content = await response.Content.ReadAsStringAsync();
result = Newtonsoft.Json.JsonConvert.DeserializeObject<List<StockItem>>(content);
}
return result;
}
Edit 1
I am trying to use the below however I am getting an error
public async Task<List<StockItem>> GetStockItemInfo()
{
return await dataTransfer.GetDataFromSageService(Constants.GetStockItems, string.Empty)) ?? new List<StockItem>();
}
Severity Code Description Project File Line Suppression State
Error CS1061 'StockTakeDT' does not contain a definition for 'GetStockDataFromSage' and no accessible extension method 'GetStockDataFromSage' accepting a first argument of type 'StockTakeDT' could be found (are you missing a using directive or an assembly reference?) StockAppDL D:\Git\Repos\StockApp\FStockApp\StockAppDal\StockDatabase.cs 76 Active
Your objective here appears to be to call an endpoint and get the results back into an object you can use. If the call is successful, you return the result and if it fails, you return an empty list.
We can abstract that logic out into a generic method that accepts a url and parameters and returns an object.
public async Task<T> GetObjectFromEndpoint<T>(string url, params string[] args)
where T : class
{
var uri = new Uri(string.Format(url, args));
var response = await _client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(content);
}
return default(T);
}
Now your GetStockDataFromSage function passes in the information unique to this call, namely the url, parameters, and generic type for the results. If the result is null, GetStockDataFromSage returns an empty list of StockItems
public async Task<List<StockItem>> GetStockDataFromSage()
{
return (await GetObjectFromEndpoint<List<StockItem>>(Constants.GetStockItems, string.Empty)) ?? new List<StockItem>();
}
Any time you are trying to minimize repetition, you want to look at what is specific to this call and what is more general. i.e List<StockItem>, the url, and possibly the parameter are unique, but the rest of the code is very general.
Caution: This method of returning a default value when the api call fails can lead to hard-to-diagnose issues where you will be unable to differentiate between an empty list and a failed api call. I recommend adding some logging for failed api calls and perhaps looking at ways to inform the calling code that the result was in error.
I am trying to get result from Controller which calls Repository which extracts a ViewModel at the end, this is for Unit testing. The bold below is not working.
Compile error:
IAction Result does not contain a definition for View and no extension method View accepting a first argument of type IAction Result
public async Task<IActionResult> Search(int? id)
{
return View(await _repository.GetProductData(id));
}
var searchresult1 = await _controller.Search(5);
var modelresult = searchresult1.View.ProductName;
try something like this
var searchresult1 = await _controller.Search(5) as ViewResult;
var result = searchResult.ViewName; // this should be the value returned from the controller
Or you can return the result as a ViewData.Model. So in your controller, you can probably do something like
return View("Product", await _repository.GetProductData(id));
Then in your test you can access the viewdata.model like
var result = await _controller.Search(5);
var productData = (ProductData) result.ViewData.Model; // cast it to whatever class the GetProductData(5) returns
Option 1 works at run time as the view name is set by the framework using route data. it is not available during unit testing. Which is why setting the view name manually in the second example works
Please see more details about MVC view model
I'm trying to unit test my web services using Moq, and I have the following test:
[TestMethod()]
public void GetCoursesTest()
{
var response = _coursesController.GetCourses();
var contentResult = response as OkNegotiatedContentResult<List<Course>>;
Assert.AreEqual(contentResult.Content.Count(), _educationCoursesData.Count);
}
Here, GetCourses() is:
public IHttpActionResult GetCourses()
{
try
{
return Ok(_entities.Courses.ToList().Select(course => new CourseDTO
{
Id = course.Id,
CrsName = course.CrsName,
CrsHour = course.CrsHour,
CrsDay = course.CrsDay,
CrsMin = course.CrsMin,
Teacher = course.Teacher
}));
}
catch
{
return InternalServerError();
}
}
So its result is a list of obejcts. However, when I try to grab the list of these objects into contentResult via response as OkNegotiatedContentResult<List<Course>>;, contentResult is just a null. How can I read the list of objects from the OkResult into a variable that I can do Count() on?
If contentResult is null after
var contentResult = response as OkNegotiatedContentResult<List<Course>>;
the cast failed. The reason is you are using .ToList() to early. By using .Select(...) you are creating an IEnumerable and no List. Try this one:
return Ok(_entities.Courses.Select(course => new CourseDTO
{
Id = course.Id,
CrsName = course.CrsName,
CrsHour = course.CrsHour,
CrsDay = course.CrsDay,
CrsMin = course.CrsMin,
Teacher = course.Teacher
})ToList());
Alternatively change the cast to
OkNegotiatedContentResult<IEnumerable<Course>>;
The method under test is not returning OkNegotiatedContentResult<List<Course>> as the content was not enumerated. Either change the expected return type (OkNegotiatedContentResult<IEnumerable<Course>>) or update the action to enumerate the collect (.ToList()) before returning its data.
I have the following Details action in SampleController:
public ActionResult Details(int sampleNumber)
{
var sample = (Sample)Session["sample"];
if (sample == null)
{
var pallet = (Pallet)Session["pallet"];
sample = pallet.Samples.First(s
=> s.SampleNo.Equals(sampleNumber));
if (sample.Defects.Count < 1) // Postback issue?
{
var access = new Access();
sample.Defects = access.GetDefects(pallet.Grv.GRVNo,
pallet.PalletSeq, sampleNumber);
sample.GetImagePaths();
sample.Pallet = pallet;
Session["sample"] = sample;
}
}
return View(sample);
}
And this Update action:
public ActionResult Update(IEnumerable<HttpPostedFileBase> files, Sample sample)
{
var pallet = (Pallet)Session["pallet"];
sample.Pallet = pallet;
sample.SaveImages(files);
access.UpdateSample(sample);
access.UpdateDefects(sample);
Session["sample"] = sample;
return View("Details", sample);
}
I am trying to debug an issue, but somehow the line return View("Details", sample); is not calling the above Details action (the breakpoint does not stop the code).
It does return a view of the selected sample, but none of the operations present in Details are occurring.
I tried changing the return statement to
return View("Details", sample.SampleNo);
To match the signature of Details, but then I get :
The model item passed into the dictionary is of type 'System.Int32', but this dictionary requires a model item of type 'MVCQCPage.Models.Sample'.
How is that possible? the Details action does NOT ask for a Sample param, so why does this not just return Details and pass in the sampleNo (int) value?
Note that the above Details action is the only method of that name in SampleController.
You need to do RedirectToAction
public ActionResult Update(IEnumerable<HttpPostedFileBase> files, Sample sample)
{
var pallet = (Pallet)Session["pallet"];
sample.Pallet = pallet;
sample.SaveImages(files);
access.UpdateSample(sample);
access.UpdateDefects(sample);
Session["sample"] = sample;
return RedirectToAction("Details", sample.SampleNo);
}
Please check https://msdn.microsoft.com/en-us/library/system.web.mvc.controller.redirecttoaction(v=vs.118).aspx
As the other answers mention, I need to use RedirectToAction.
However, i also need to pass in a named sampleNumber parameter:
return RedirectToAction("Details", new { #sampleNumber = sample.SampleNo });
You need to use RedirectToAction:
public ActionResult Update(IEnumerable<HttpPostedFileBase> files, Sample sample)
{
var pallet = (Pallet)Session["pallet"];
sample.Pallet = pallet;
sample.SaveImages(files);
access.UpdateSample(sample);
access.UpdateDefects(sample);
Session["sample"] = sample;
return RedirectToAction("Details", sample);
}
The View() method returns the specified view without invoking the Details Action, however the RedirectToAction() method redirects to the specified action not the View().