0

I have the following controller which retrieves data from an endpoint.

It can also sort data depending on whether or not type is set.

What would be the best approach to testing it?

public class UsersController : ControllerBase
    {

        [HttpGet]
        public async Task<IActionResult> GetAllUsers(string type)
        {
            using (var client = new HttpClient())
            {
                try
                {
                    client.BaseAddress = new Uri("http://demo10102020.mockable.io");
                    var response = await client.GetAsync($"/people");
                    response.EnsureSuccessStatusCode();

                    var stringResult = await response.Content.ReadAsStringAsync();
                    List<User> rawUsers = JsonConvert.DeserializeObject<User[]>(stringResult).ToList();
                    List<User> sortedUsers = rawUsers;
                    if(type == "first-name")
                    {
                        sortedUsers = rawUsers.OrderBy(o => o.FirstName).ToList();
                    }
                    else if(type == "score")
                    {
                        sortedUsers = rawUsers.OrderBy(o => o.Score).ToList();
                    }

                    return Ok(sortedUsers);
                }
                catch (HttpRequestException httpRequestException)
                {
                    return BadRequest($"Error getting users: {httpRequestException.Message}");
                }
            }
        }
    }

This is my approach so far, I am not sure how to mock the API:

[TestClass]
public class TestPersonController
{
    [TestMethod]
    public void GetAllPersons_ShouldReturnAllProducts()
    {
        var testPersons = GetTestPersons();
        var controller = new PersonController();
    }

    private List<Person> GetTestPersons()
    {
        var testPersons = new List<Person>();
        testPersons.Add(new Person { FirstName = "dfdfdf", Surname = "dfdfdf", Score = 100 });
        testPersons.Add(new Person { FirstName = "dfsdfsfasf", Surname = "safasfsdaf", Score = 200 });
        testPersons.Add(new Person { FirstName = "asffas", Surname = "asdffasdf", Score = 200 });

        return testPersons;
    }
}
methuselah
  • 12,766
  • 47
  • 165
  • 315
  • What's wrong with your current (not yet shown) approach? – Alexei Levenkov Jan 10 '20 at 00:50
  • I assume you are using the Newtonsoft JSON library? Just curious that you don't get a pre-compile warning that the `DeserializeObject` method could throw an exception that won't be caught in the `HttpRequestException` block.--sorry this doesn't answer your question – Barns Jan 10 '20 at 01:06
  • As far as your test case goes it would be easier to separate the method `GetAllUsers` into two methods--one to handle the `GetAsync`. Then pass the Response string to another method for the deserialization. Also, it is the general recommendation to include 'Async' in any method name where the method performs an async operation like `GetAllUsersAsync()` – Barns Jan 10 '20 at 01:16
  • *it can also sort* Great anti-example of [SRP](https://en.wikipedia.org/wiki/Single_responsibility_principle). Logic should be extracted from the method so that communicating with a third party is abstracted out. That way you'd have no trouble testing the sorting logic. Is it an option to refactor the code under test? – John Wu Jan 10 '20 at 01:17

2 Answers2

1

Not sure if its worth but i would go about, moving the api call to a separate factory class and the sorting. Controller to only get the final value from your factory. I may have an orchestrator factory to call the api invoking factory and do the sorting.

Then, the Unit test can be done for

  1. api factory by extending HttpClient and create methods to return test/mock data.

  2. orchestrator factory to expect the list of strings or input data and test the sorted data.

  3. then comes the test for the controller

Ak777
  • 346
  • 7
  • 18
  • Thanks, how would the api call test look like? – methuselah Jan 10 '20 at 05:06
  • I will share some by end of day today – Ak777 Jan 10 '20 at 15:12
  • 1
    I finally was able to create one closer to what you are looking for, but generalized to an extent with the time i had to come up with. You can find the sample demo code at https://gitlab.com/AravindK777/friendzardemoapp – Ak777 Jan 13 '20 at 18:44
1

just as an opinion, I think your method does too many things

I would put in another method the call Http

For example :

        [HttpGet]
        public async Task <IActionResult> GetAllUsers (string type)
        {
           
                    List <User> rawUsers = UserExternalService.GetAllUser()

                    if (type == "first-name")
                    {
                        return Ok (rawUsers.OrderBy (o => o.FirstName) .ToList ());
                    }
                    else if (type == "score")
                    {
                        return Ok (rawUsers.OrderBy (o => o.Score) .ToList ());
                    }

                    return Ok (rawUsers);
            
        }

This method would take 3 unit test

Example:

        public class The_Method_GetAllUsers
        {
              
            [Fact]
            public async void Should_return_user_when_type_is_name
            {
               Assert.IsType<OkObjectResult>>(this.Sut.GetAllUser("name"));
            }

            [Fact]
            public async void Should_return_user_when_type_is_score
            {
               Assert.IsType <OkObjectResult>>(this.Sut.GetAllUser("score"));
            }

            [Fact]
            public async void Should_return_user_when_type_its_not_name_and_score
            {
               Assert.IsType <OkObjectResult>>(this.Sut.GetAllUser("surname"));
            }
        }

I think it is a cleaner solution, it would be missing the unit test of the service that we believe is only called http and with its try / catch

Mock HttpClient Mock HttpClient using Moq

Alimentador
  • 148
  • 1
  • 7