We will now write tests for the Fruit
Controller. As discussed before there are three steps that make up a unit test - Arrange, Act, Assert. We will go through each of these steps in this article. First create an Empty class in RestTestUNIT
project, let's call it FruitControllerTest.cs
. Add the following to it.
namespace RestTestUNIT;
using RestTest.Service;
using RestTest.Controllers;
public class FruitControllerTest
{
private FruitBAsketController fruitbasketcontroller;
[SetUp]
public void Setup()
{
}
[Test]
public void GetFruitsTest()
{
Assert.Pass();
}
}
In .NET, we can associate functions inside the class with different testing tags. Some of these are:
-
OneTimeSetup
- to define logic that run only once while running the Tests. -
Setup
- to definde code that must be executed before each testcase -
Test
- marks a function as test function -
TestCase()
- used to pass parameters to the test functions
Arrange
We need to provide the input parameters and the expected result in this step. We have a very simple api with just static data instead of data from a database, but in real applications this is not the case. For testing there, it is not viable to make database requests for each test. Thus there we use something called as Mock Response. We will try to use the same approach here as well. To mock data, we will use a package called Moq
. Go ahead and install Moq from Nuget manager.
Lets create some responses now. We will test the following cases:
- When no argument is passed : this should return a list of fruits with no filter
- When
fruitname
is passed: this should only return a list of fruits with this name - When
fruitname
andsorted
is passed: this should return fruit list with the same name and state ofsorted
- When
fruitname
is not present : this should return aBadRequest
response.
Add the following objects to the FruitControllerTest
class
private string allfruits = @"
[
{
'name': 'Apple',
'price': 43,
'sorted': false
},
{
'name': 'Litchi',
'price': 32,
'sorted': false
},
{
'name': 'Litchi',
'price': 32,
'sorted': true
},
{
'name': 'Mango',
'price': 32,
'sorted': false
},
{
'name': 'Pineapple',
'price': 26,
'sorted': false
},
{
'name': 'Apple',
'price': 20,
'sorted': false
}
]";
private string onlyapple = @"[
{
'name': 'Apple',
'price': 43,
'sorted': false
},
{
'name': 'Apple',
'price': 20,
'sorted': false
},
{
'name': 'Apple',
'price': 32,
'sorted': true
}
]";
private string onlytrueapple = @"[
{
'name': 'Apple',
'price': 32,
'sorted': true
}
]";
private fruitList = @"";
We will use these as the mocked responses. Since the data is returned from GetFruits()
in the FruitService
class, We will mock the response for this Service. For that, we will use the interface IFruitService
as interface only contains the declaration of the function and not its implementation.
In FruitBasketController
add another constructor that initialises the controller with the interface instead of the actual service
public FruitBAsketController(Object fruitServiceObject)
{
fruitservice = (IFruitService)fruitServiceObject;
}
We will use this constructor for our mocked response. In the Setup()
we will setup our controller for testing. Add the following in the Setup()
private Mock<IFruitService> fruitmock;
[SetUp]
public void Setup()
{
fruitmock = new Mock<IFruitService>();
fruitbasketcontroller = new FruitBAsketController(fruitmock.Object);
}
Here we are setting up such that the fruitbasketcontroller will now refer the fruitmock
object now when calling the service, instead of the actual definition.
Act
In this step, we will call our functions to return functions to return the above responses based on different test cases. Add the following function in FruitControllerTest.cs
[Test]
[TestCase("",false)]
[TestCase("Apple", false)]
[TestCase("Apple", true)]
[TestCase("Kiwi", false)]
public void GetFruitsTest(string fruitname="",bool sorted = false)
{
List<Fruit>? a = null;
if(!sorted)
{
if (fruitname.Length == 0) // get all fruits
fruitList = allfruits;
else if (fruitname.Length > 0)
if (fruitname == "Apple" || fruitname == "apple") // get only apples
fruitList = onlyapple;
}
else // sorted is true
{
if (fruitname.Length > 0)
if (fruitname == "Apple" || fruitname == "apple") // only true apple
fruitList = onlytrueapple;
//if the fruit does not exist fruitList is empty
}
if(fruitList.Length > 0)
a = JsonConvert.DeserializeObject<List<Fruit>>(fruitList);
fruitmock.Setup(x => x.GetFruits("", false)).Returns(a);
var res = fruitbasketcontroller.GetAllFruits("", false);
The different values for parameters are passed through the TestCase
tag. Using this parameters, we populate the list fruitList
which is then converted into List<Fruit>
using JsonConvert
.
Since we are using a mock object for FruitService
, we can set it up to return response based on the above steps. This means it does not go into actual definition of functions in FruitService
class but just mocks the response.
Assert
In the third stage, we will assert the responses ,i.e, we will validate the response returned from the function. Add the following Assertions just below the above code in GetFruitsTest()
.
var res = fruitbasketcontroller.GetAllFruits(fruitname, sorted);
if(((IStatusCodeActionResult)res).StatusCode == 200)
{
var okObject = res as OkObjectResult;
var fruitList = okObject.Value as List<Fruit>;
foreach(Fruit obj in fruitList)
{
Assert.IsNotNull(obj.name);
if (fruitname.Length > 0)
Assert.True(fruitname.Equals(obj.name, StringComparison.OrdinalIgnoreCase));
Assert.IsNotNull(obj.price);
Assert.IsNotNull(obj.sorted);
if (sorted)
Assert.True(obj.sorted == sorted);
}
}
else if(((IStatusCodeActionResult)res).StatusCode == 400)
{
var badRequest = (res as BadRequestObjectResult).Value;
Assert.True(badRequest == "Found Nothing");
}
Since the Service can return two types of responses with status code 200 and 400, we will make our assertions accordingly. And with that we have successfully written Unit Test for out controller. Go ahead and run these tests from Test Explorer.
Thanks for reading!
Top comments (0)