I am trying to create an Integration Test for my .Net Core Web Api
But I am always getting a 400 Bad Request response. I am sharing details below
Here is my Controller method
public IActionResult UpdateProductById([FromBody]int id, string description)
{
var result = ProductService.UpdateProductById(id, description);
if (result.Exception == null)
return Ok(result);
else
return BadRequest(result.Exception.Message);
}
Here is my test class (which tries to post)
[Fact]
public async Task UpdateProductById_Test_WithProduct()
{
var product = new
{
id = 1,
description = "foo"
};
var productObj= JsonConvert.SerializeObject(product);
var buffer = System.Text.Encoding.UTF8.GetBytes(productObj);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var result = await _tester.Client.PostAsync("/api/1.0/UpdateProductById", byteContent);
result.StatusCode.Should().Be(HttpStatusCode.OK);
}
The test is sending all the content in the body of the request, yet the action is only binding the id. Most likely the description is null and causing an issue with the update.
Create a model to hold the data
public class ProductModel {
public int id { get; set; }
public string description { get; set; }
}
Refactor action to get the content from the body of the request
[HttpPost]
public IActionResult UpdateProductById([FromBody]ProductModel model) {
if(ModelState.IsValid) {
var result = ProductService.UpdateProductById(model.id, model.description);
if (result.Exception == null)
return Ok(result);
else
return BadRequest(result.Exception.Message);
}
return BadRequest(ModelState);
}
Related
I have the following post method to receive id and image multipart/form-Data and save it in sql server database
[Route("api/save_cus")]
[HttpPost]
public async Task<IHttpActionResult> save_cus([FromBody] MultipartFileData CUS_PHOTO,int id)
{
if (!Request.Content.IsMimeMultipartContent())
{
return Ok( "UnsupportedMediaType");
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
if (Directory.Exists(root) == false) Directory.CreateDirectory(root);
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// we take the first file here
CUS_PHOTO = provider.FileData[0];
// and the associated datas
int myInteger;
if (int.TryParse(provider.FormData["MyIntergerData"], out myInteger) == false)
throw new ArgumentException("myInteger is missing or not valid.");
var fileContent = File.ReadAllBytes(CUS_PHOTO.LocalFileName);
var Customers = db.Customers.where(a=> a.id == id)
Customers.CUS_PHOTO =fileContent ;
db.SaveChangesAsync();
}
and me model look somthing like this
public class CUSTOMERS
{
public int id { get; set; }
public string CUS_NAME { get; set; }
public byte[] CUS_PHOTO { get; set; }
}
then when I try to post this with postman I got UnsupportedMediaType
can you help me please to know where is the problem and solve it.
Controller:
private IBeer _beerService;
public BeerController(IBeer beerService)
{
_beerService = beerService;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]Beer model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var beer = await _beerService.Add(model);
return Ok(CreatedAtAction("Add Item", new { beer.id }, beer));
}
Model:
public class Beer
{
public int id { get; set; }
public string name { get; set; }
public int cost { get; set; }
}
Interface :
public interface IBeer
{
Task<Beer> Add(Beer beer);
}
Test:
[Fact]
public void TestPostWithMock()
{
// Arrange
var serviceMock = new Mock<IBeer>();
List<Beer> expected = new List<Beer>()
{
new Beer{id=2, beer="Kingfisher", cost=170 },
};
serviceMock.Setup(x => x.Add(expected)).Returns(() => Task.FromResult(beer));
var controller = new BeerController(serviceMock.Object);
// Act
var result = controller.Get(2);
// Assert
var okResult = result.Should().BeOfType<OkObjectResult>().Subject;
var actual = okResult.Value.Should().BeAssignableTo<IEnumerable<Beer>>().Subject;
Assert.Equal(expected, actual);
}
I'm trying to mock the post request but when I try to mock it here:
x => x.Add(expected)
It gives me an error - Generic.list cannot be converted to Controller.Beer. What should be done ?
As error description mentions, you send list of beer (List<Beer>) to your Add() method while setuping your service. You should instead send Beer class instance (like in your interface) as parameter like shown below.
// Update your request object without List
var expected = new Beer()
{
id=2,
beer="Kingfisher",
cost=170
};
And also, update your Setup function as below.
serviceMock.Setup(x => x.Add(expected)).Returns(() => Task.FromResult(expected));
Lastly, you need to get your result from controller.Post() method as in the controller, not from controller.Get(2). Hope this helps you.
I am trying to learn xamarin forms. I have this existing database that I wanted to be accessed by an App. Here I want to update something using web api/REST, I followed this guide for consuming REST. Unfortunately it's not working and I don't even know why.
How do I properly use PUT for updating something and what is wrong here?
WEB API class:
class GuestAcc
{
public string RefCode { get; set; }
public double Price { get; set; }
}
Xamarin Model:
public class GuestAcc
{
public string RefCode { get; set; }
public double Price { get; set; }
}
GuestAccountsController:
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> UpdateBalance(GuestAcc guestAcc)
{
var guestAccounts = db.GuestAccounts.First(x => x.ReferenceCode == guestAcc.RefCode);
guestAccounts.Balance = guestAccounts.Balance - guestAcc.Price;
db.Entry(guestAccounts).State = EntityState.Modified;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
Xamarin form:
private async void btn_proceed_clicked(object sender, EventArgs e)
{
GuestAcc guestAcc = new GuestAcc();
guestAcc.Price = 125;
guestAcc.RefCode = "user123";
var guestAccountURL = "http://192.168.8.100:56750/api/UpdateBalance/";
var uri_guestAccount = new Uri(string.Format(guestAccountURL, string.Empty));
var json = JsonConvert.SerializeObject(guestAcc);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
response = await client.PutAsync(uri_guestAccount, content);
if (response.IsSuccessStatusCode)
{
await DisplayAlert("Notice", "Success", "Ok");
}
}
You need to use the correct HTTP verb either in the action name or as an attribute to the method. like
[ResponseType(typeof(void))]
[HttpPut]
public async Task<IHttpActionResult> UpdateBalance(GuestAcc guestAcc)
{
var guestAccounts = db.GuestAccounts.First(x => x.ReferenceCode == guestAcc.RefCode);
guestAccounts.Balance = guestAccounts.Balance - guestAcc.Price;
db.Entry(guestAccounts).State = EntityState.Modified;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
or in the name like
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutBalanceAsync(GuestAcc guestAcc)
{
var guestAccounts = db.GuestAccounts.First(x => x.ReferenceCode == guestAcc.RefCode);
guestAccounts.Balance = guestAccounts.Balance - guestAcc.Price;
db.Entry(guestAccounts).State = EntityState.Modified;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.NoContent);
}
I have a post request for my PhotosController class. When I test this code, it always returns a null value. I don't see file details.
Basically it gets the userid and PhotoDto and it should return photo. I use Cloudinary service to store photos. My API settings of the clodinary is located inside appsettings.json file and there is no problem for those settings. When I debug the code, the problem occurs where if (file.Length > 0) is. I am guessing that there is no file.
Here is my PhotoForCreationDto file:
public class PhotoForCreationDto
{
public string Url { get; set; }
public IFormFile File { get; set; }
public string Description { get; set; }
public DateTime DateAdded { get; set; }
public string PublicId { get; set; }
public PhotoForCreationDto()
{
DateAdded = DateTime.Now;
}
}
And here is my PhotosController file:
[Authorize]
[Route("api/users/{userId}/photos")]
public class PhotosController : Controller
{
private readonly IDatingRepository _repo;
private readonly IMapper _mapper;
private readonly IOptions<CloudinarySettings> _cloudinaryConfig;
private Cloudinary _cloudinary;
public PhotosController(IDatingRepository repo,
IMapper mapper,
IOptions<CloudinarySettings> cloudinaryConfig)
{
_mapper = mapper;
_repo = repo;
_cloudinaryConfig = cloudinaryConfig;
Account acc = new Account(
_cloudinaryConfig.Value.CloudName,
_cloudinaryConfig.Value.ApiKey,
_cloudinaryConfig.Value.ApiSecret
);
_cloudinary = new Cloudinary(acc);
}
[HttpGet("{id}", Name = "GetPhoto")]
public async Task<IActionResult> GetPhoto(int id)
{
var photoFromRepo = await _repo.GetPhoto(id);
var photo = _mapper.Map<PhotoForReturnDto>(photoFromRepo);
return Ok(photo);
}
[HttpPost]
public async Task<IActionResult> AddPhotoForUser(int userId, PhotoForCreationDto photoDto)
{
var user = await _repo.GetUser(userId);
if (user == null)
return BadRequest("Could not find user");
var currentUserId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
if (currentUserId != user.Id)
return Unauthorized();
var file = photoDto.File;
var uploadResult = new ImageUploadResult();
if (file.Length > 0)
{
using (var stream = file.OpenReadStream())
{
var uploadParams = new ImageUploadParams()
{
File = new FileDescription(file.Name, stream)
};
uploadResult = _cloudinary.Upload(uploadParams);
}
}
photoDto.Url = uploadResult.Uri.ToString();
photoDto.PublicId = uploadResult.PublicId;
var photo = _mapper.Map<Photo>(photoDto);
photo.User = user;
if (!user.Photos.Any(m => m.IsMain))
photo.IsMain = true;
user.Photos.Add(photo);
if (await _repo.SaveAll())
{
var photoToReturn = _mapper.Map<PhotoForReturnDto>(photo);
return CreatedAtRoute("GetPhoto", new { id = photo.Id }, photoToReturn);
}
return BadRequest("Could not add the photo");
}
}
Here is error at postman:
I tried to use [FromBody] but it also didn't work.
I would appriciate any help.
When submitting file from Postman, make sure you don't fill Content-Type header by yourself. Postman will set it automatically to multipart/form-data value.
Setting Content-Type header to application/json prevents ASP.Net Core from correct processing of request data. That's why IFormFile property is not filled and is set to null.
I am trying to get this JSON response with an Ihttpstatus header that states code 201 and keep IHttpActionResult as my method return type.
JSON I want returned:
{"CustomerID": 324}
My method:
[Route("api/createcustomer")]
[HttpPost]
[ResponseType(typeof(Customer))]
public IHttpActionResult CreateCustomer()
{
Customer NewCustomer = CustomerRepository.Add();
return CreatedAtRoute<Customer>("DefaultApi", new controller="customercontroller", CustomerID = NewCustomer.ID }, NewCustomer);
}
JSON returned:
"ID": 324,
"Date": "2014-06-18T17:35:07.8095813-07:00",
Here are some of the returns I've tried that either gave me uri null error or have given me a response similar to the example one above.
return Created<Customer>(Request.RequestUri + NewCustomer.ID.ToString(), NewCustomer.ID.ToString());
return CreatedAtRoute<Customer>("DefaultApi", new { CustomerID = NewCustomer.ID }, NewCustomer);
With an httpresponsemessage type method this can be solved as shown below. However I want to use an IHttpActionResult:
public HttpResponseMessage CreateCustomer()
{
Customer NewCustomer = CustomerRepository.Add();
return Request.CreateResponse(HttpStatusCode.Created, new { CustomerID = NewCustomer.ID });
}
This will get you your result:
[Route("api/createcustomer")]
[HttpPost]
//[ResponseType(typeof(Customer))]
public IHttpActionResult CreateCustomer()
{
...
string location = Request.RequestUri + "/" + NewCustomer.ID.ToString();
return Created(location, new { CustomerId = NewCustomer.ID });
}
Now the ResponseType does not match. If you need this attribute you'll need to create a new return type instead of using an anonymous type.
public class CreatedCustomerResponse
{
public int CustomerId { get; set; }
}
[Route("api/createcustomer")]
[HttpPost]
[ResponseType(typeof(CreatedCustomerResponse))]
public IHttpActionResult CreateCustomer()
{
...
string location = Request.RequestUri + "/" + NewCustomer.ID.ToString();
return Created(location, new CreatedCustomerResponse { CustomerId = NewCustomer.ID });
}
Another way to do this is to use the DataContractAttribute on your Customer class to control the serialization.
[DataContract(Name="Customer")]
public class Customer
{
[DataMember(Name="CustomerId")]
public int ID { get; set; }
// DataMember omitted
public DateTime? Date { get; set; }
}
Then just return the created model
return Created(location, NewCustomer);
// or
return CreatedAtRoute<Customer>("DefaultApi", new controller="customercontroller", CustomerID = NewCustomer.ID }, NewCustomer);