简体   繁体   中英

Testing an action method with external dependencies

This is an example of an Action method inside HomeController :

[HttpPost]
public async Task<dynamic> UnitTest(string data)
{
    var httpClient = new HttpClient();
    var request = JsonConvert.SerializeObject(data);
    var url = "https://jsonplaceholder.typicode.com/posts";
    var response = await httpClient.PostAsync(url, new StringContent(request, Encoding.UTF8, "application/json"));
    string responseContent = await response.Content.ReadAsStringAsync();
    return responseContent;
}

I want to test it, but I do not know how. I tried the following:

[TestMethod]
public async Task JsonRightTest()
{
    MyModelR model1 = new MyModelR
    {
        Title = "foo",
        Body = "bar",
        UserId = 1
    };
    string output1 = JsonConvert.SerializeObject(model1);
    var url = "Home/UnitTest";
    var response = await _client.PostAsync(url, new StringContent(output1, Encoding.UTF8, "application/json"));
    response.EnsureSuccessStatusCode();

    var responseContent = await response.Content.ReadAsStringAsync();
    var responseModel = JsonConvert.DeserializeObject<MyModel>(responseContent);


    // Assert
    Assert.AreEqual(1,
        responseModel.UserId);
}


internal class MyModel
{
    public string Title { get; set; }
    public string Body { get; set; }
    public int UserId { get; set; }
    public int Id { get; set; }
}

internal class MyModelR
{
    public string Title { get; set; }
    public string Body { get; set; }
    public int UserId { get; set; }
}

Unfortunately, the above does not work. Since I am very confused could you give me some answers to the following:

  • What's the best way to test UnitTest action? Is my approach wrong? Do I just have to call the API from JsonRightTest method and not involve the action ?
  • Actually, in that case do we have a unit or integrated test?

I want to call the actual external end point.

The API ( https://jsonplaceholder.typicode.com/posts ) is found on the Internet and is available for testing purposes.

This appears to be an XY problem and a mixing of concerns.

The code under test is tightly coupled to implementation concerns and should encapsulate that external call behind a service abstraction that can be mocked during isolated unit tests.

Some refactoring steps that should be followed....

Those models being constructed in the test should be in the action.

[HttpPost]
public async Task<IActionResult> UnitTest([FromBody]MyDataR data) {
    var httpClient = new HttpClient();
    var requestJson = JsonConvert.SerializeObject(data);
    var url = "https://jsonplaceholder.typicode.com/posts";
    var response = await httpClient.PostAsync(url, new StringContent(requestJson, Encoding.UTF8, "application/json"));
    if(response.IsSuccessStatusCode) {
        var responseContent = await response.Content.ReadAsStringAsync();
        var responseModel = JsonConvert.DeserializeObject<MyModel>(responseContent);        
        return Ok(responseModel);
    }else 
        return StatusCode(response.StatusCode);
}

Refactoring further, the actual calling of the external endpoint should be abstracted away

public interface IExternalService {
    Task<MyModel> PostDataAsync(MyData data);
}

and implemented accordingly

public class ExternalService : IExternalService  {
    // should consider abstracting this as well but that is another matter
    static Lazy<HttpClient> _httpClient = new Lazy<HttpClient>(() => new HttpClient());

    private HttpClient httpClient {
        get { return _httpClient.Value; }
    }       

    public async Task<MyModel> PostDataAsync(MyData data) {
        var requestJson = JsonConvert.SerializeObject(data);
        var url = "https://jsonplaceholder.typicode.com/posts";
        var content = new StringContent(requestJson, Encoding.UTF8, "application/json")
        var response = await httpClient.PostAsync(url, content);
        var responseContent = await response.Content.ReadAsStringAsync();
        if(response.IsSuccessStatusCode) {
            var responseContent = await response.Content.ReadAsStringAsync();
            var responseModel = JsonConvert.DeserializeObject<MyModel>(responseContent);        
            return responseModel;
        }else 
            return null;
    }
}

with the action in the the controller now looking like

private readonly IExternalService externalService; // Assumed injected into the controller

[HttpPost]
public async Task<IActionResult> UnitTest([FromBody]MyDataR data) {
    var responseModel = await externalService.PostDataAsync(data);
    if(responseModel != null) {
        return Ok(responseModel);
    }else 
        return BadRequest();
}

By removing the tight coupling to the external service call , this would allow the controller to be tested in isolation as needed to verify that it behaves as expected.

The external service call implementation can now be tested on its own if the desire is to check that the external end point behaves as expected. This would be considered an integration test due to its reliance on the actual external endpoint.

[TestMethod]
public async Task JsonRightTest() {
    // Arrange
    var expected = 1;

    var model = new MyModelR {
        Title = "foo",
        Body = "bar",
        UserId = 1
    };

    var target = new ExternalService(); // System under test

    // Act
    var responseModel = await target.PostDataAsync(model);

    // Assert
    Assert.IsNotNull(responseModel);
    var actual = responseModel.UserId;
    Assert.AreEqual(expected, actual);
}

This should now allow for easier inspection of the external service to verify that it behaves as expected.

In production you would make sure that the external service abstraction and its implementation are registered in the composition root.

services.AddTransient<IExternalService, ExternalService>();

so that it is injected into the dependent controller correctly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM