unit testing an iformfile that is converted into system.drawing.image in c#

In this blog post I’m going to be covering a very specific issue. Let’s imagine the following scenario; we have a RESTful API running on .NET Core that uses a series of classes, disguised as services, (again running on .NET Core) as it’s business layer. The RESTful API receives HTTP requests, said requests are processed by the services, there’s some data manipulation happening, and then a result is returned back. The class library project would have a unit test project that tests the functionality inside it. So far, I’d like to think, is rather clear and quite a standard approach too. The following is our MVC controller that receives requests related to images. The image service is injection via Dependency Injection and then the method SaveImage() is called.

[Route("api/[controller]")]
[ApiController]
public class ImagesController : ControllerBase
{
private readonly IImageService _imageService;
public ImagesController(IImageService imageService)
{
_imageService = imageService;
}
// POST api/images
[HttpPost]
public async Task<IActionResult> PostAsync([FromForm] ImageDetailsDto imageDetails)
{
var requestImage = Request.Form.Files.FirstOrDefault();
var result = await _imageService.SaveImage(imageDetails.UserId, requestImage);
// save user details in some other service
return Ok(result);
}
}

The image service (which inherits from an interface) grabs the IFormFile object, converts it to an Image (from the Nuget Package System.Drawing), checks the width and height, and saves accordingly. That implementation can be found in the following snippet.

public class ImageService : IImageService
{
public async Task<string> SaveImage(int userId, IFormFile uploadedImage)
{
// convert IFormFile to Image and validate
using (var image = Image.FromStream(uploadedImage.OpenReadStream()))
{
if (image.Width > 640 || image.Height > 480)
{
// do some resizing and then save image
}
else
{
// save original image for user
}
}
return "image saved";
}
}

Eventually we’re going to want to unit test this interface and this is where I ran into an issue. I was able to create a mock, so to speak, IFormFile and mimic the behaviour of an image as one of the parameters of the method SaveImage, but as soon as I tried to convert that mocked IFormFile into an Image my program threw an exception. From the way I understood it, the IFormFile is essentially a Stream. In an actual HTTP request that Stream represents an image (with all it’s graphical data compressed in that Stream) and is compatible with the object Image (Sytem.Drawing) but when I created a random Stream for my unit test, that Stream is lacking graphical data and therefore cannot be converted to an Image. I then started digging on Google, and StackOverflow, and thanks to this guy’s blog post I came up with a solution. Create an actual graphical image, convert it into a Stream and then inject that in the test, as you can see below.

public class ImageServiceTests
{
private readonly IImageService _imageService;
private readonly int _userId = 1234;
public ImageServiceTests()
{
_imageService = new ImageService();
}
[Fact]
public async Task Service_Saves_Image()
{
// Arrange
var expected = "image saved";
var imageStream = new MemoryStream(GenerateImageByteArray());
var image = new FormFile(imageStream, 0, imageStream.Length, "UnitTest", "UnitTest.jpg")
{
Headers = new HeaderDictionary(),
ContentType = "image/jpeg"
};
// Act
var result = await _imageService.SaveImage(_userId, image);
// Assert
Assert.Equal(expected, result);
}
private byte[] GenerateImageByteArray(int width = 50, int height = 50)
{
Bitmap bitmapImage = new Bitmap(width, height);
Graphics imageData = Graphics.FromImage(bitmapImage);
imageData.DrawLine(new Pen(Color.Blue), 0, 0, width, height);
MemoryStream memoryStream = new MemoryStream();
byte[] byteArray;
using (memoryStream)
{
bitmapImage.Save(memoryStream, ImageFormat.Jpeg);
byteArray = memoryStream.ToArray();
}
return byteArray;
}
}

I also took the liberty of uploading the solution to my github in case anyone would want to have a better look at it. I hope this post has been helpful to you and thanks for reading 🙂

Until next post,
Bjorn

Leave a comment