DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • How to Enhance the Performance of .NET Core Applications for Large Responses
  • Developing Minimal APIs Quickly With Open Source ASP.NET Core
  • Revolutionizing Content Management
  • Revolutionizing API Development: A Journey Through Clean Architecture With Adapter Pattern in ASP.NET Core

Trending

  • Ujorm3: A New Lightweight ORM for JavaBeans and Records
  • Building an Image Classification Pipeline With Apache Camel and Deep Java Library (DJL)
  • End-to-End Event Streaming With Kafka, Spring Boot and AWS SQS/SNS (Production-Ready Code Guide)
  • RAG Done Right: When to Use SQL, Search, and Vector Retrieval and How To Combine Them
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Functional Testing For ASP.NET Core API

Functional Testing For ASP.NET Core API

Functional testing is the next level above the unit testing and gives us confidence that the code we wrote works as intended.

By 
Andrew Kulta user avatar
Andrew Kulta
·
Mar. 10, 22 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
6.8K Views

Join the DZone community and get the full member experience.

Join For Free

What Is Functional Testing, Actually?

Functional testing is the process through which we determine if a piece of software is acting following pre-determined requirements. It uses black-box testing techniques, in which the tester does not know the internal system logic.

In the scope of API, we need to prepare possible requests that we expect to get and be sure that endpoints return a proper response for any payload.

Should We Even Spend Time on It?

Definitely! If you don't write integrational tests, then functional testing is a solution to ensure that your code has fewer bugs than it could have. When you can write integrational tests as well, it should be simple to reuse a shared codebase for functional and integrational tests. One difference will be in the proper environmental setup.

Let's Define a Simple API

Our controller contains only single action to create some entity for test purposes. Our business rules may require any entity, which doesn't matter for our test example. Besides, we should remember about authentication nowadays. Security compliance requires protecting API with any authentication. Let's assume that the current API requires an Authorization header with a Bearer token.

C#
 
[ApiController]
public class MyController : ControllerBase
{
    private readonly IService _service;
    
    public MyController(IService service)
    {
        _service = service;
    }
   
    [HttpPost("entities")]
    [ProducesResponseType(typeof(CreateResponse), (int)HttpStatusCode.OK)]
    [ProducesResponseType(typeof(ExceptionResponse), (int)HttpStatusCode.BadRequest)]
    public Task<CreateResponse> CreateAsync([FromBody] CreateRequest request)
    {
        return _service.CreateAsync(request);
    }
}


What Will the Functional Test Look Like for This Controller?

In our test project, I used NUnit as a test framework, but it's up to you to choose which better suits your needs. NUnit has all the required functionality to write good tests. Also, it's pretty popular on the market.

Let's add the following packages to the test project:

dotnet add package NUnitdotnet add package NUnit3TestAdapter


C#
 
[TestFixture]
public class ApiControllerTests
{
    private const string CreateUrl = "/entities";

    private readonly TokenBuilder _tokenBuilder = new();

    private TestServer? _server;
    private HttpClient? _client;

    [SetUp]
    public void Setup()
    {

        var settings = new Settings
        {
            SymmetricFuncTestKey = "your key for functional tests"
        };

        var builder = new WebHostBuilder()
            .UseStartup<Startup>()
            .ConfigureTestServices(services =>
            {
                // Mock your external services here
            })
            .ConfigureServices(services =>
            {
                // Change authentication to use bearer token which signed by symmetric key
                services.PostConfigure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        IssuerSigningKey =
                            new SymmetricSecurityKey(Encoding.UTF8.GetBytes(settings.SymmetricFuncTestKey)),
                        ValidateIssuerSigningKey = true,
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidIssuer = TestConstants.Issuer,
                        ValidAudience = TestConstants.Audience,
                    };
                });
            });
        _server = new TestServer(builder);
        _client = _server.CreateClient();
    }

    [Test]
    public async Task CreateAsync_Should_ReturnNotAuthenticated_When_RequestWithoutAuthToken()
    {
        // Arrange
        var request = new CreateRequest
        {
            PropertyName = "propertyValue"
        };
        var json = JsonConvert.SerializeObject(request);
        using var data = new StringContent(json, Encoding.UTF8, "application/json");

        // Act
        var response = await _client?.PostAsync(CreateUrl, data)!;

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
    }

    [Test]
    public async Task CreateAsync_Should_Return400_When_RequestValidationFailed()
    {
        // Arrange
        var request = new CreateRequest();
        var json = JsonConvert.SerializeObject(request);
        using var data = new StringContent(json, Encoding.UTF8, "application/json");
        var token = await _tokenBuilder.BuildJWTToken();
        _client?.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);

        // Act
        var response = await _client?.PostAsync(CreateUrl, data)!;

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }

    [Test]
    public async Task CreateAsync_Should_ReturnCreateResponse()
    {
        // Arrange
        var request = new CreateRequest
        {
            PropertyName = "propertyValue"
        };
        var json = JsonConvert.SerializeObject(request);
        using var data = new StringContent(json, Encoding.UTF8, "application/json");
        var token = await _tokenBuilder.BuildJWTToken();
        _client?.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);

        var expected = new CreateResponse
        {
            PropertyName = "propertyValue"
        };

        // Act
        var response = await _client?.PostAsync(CreateUrl, data)!;

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var responseBody = await response.Content.ReadAsStringAsync();
        var responseObject = JsonConvert.DeserializeObject<CreateResponse>(responseBody);
        responseObject.Should().BeEquivalentTo(expected);
    }
}


We should cover that API endpoint protected by authentication, and clients can't request it without a proper token. To do this, we need to prepare a test server with a proper authentication setup. To do it in Startup, we need to override JwtBearerOptions with our test authentication, which relies on auth token signed by SymmetricSecurityKey. To comply with security rules, we should avoid storing any secrets in code, and I don't suggest you violate this rule even for test projects.

When the authentication configuration is ready, we can generate the proper token to execute business logic in test cases. We can separate token generation into TokenBuilder and use it to get auth token.

Conclusion

I hope you feel that functional tests are pretty simple and allow you to verify your API each time something changes. We often include functional tests as a step in your build pipeline, and it gives you confidence that everything works well before deploying to the production environment.

Any questions or comments? Ping me on LinkedIn or leave a comment. And if you liked this post, please give it a clap and share it with all of your friends.

API Functional testing ASP.NET Testing ASP.NET Core

Published at DZone with permission of Andrew Kulta. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How to Enhance the Performance of .NET Core Applications for Large Responses
  • Developing Minimal APIs Quickly With Open Source ASP.NET Core
  • Revolutionizing Content Management
  • Revolutionizing API Development: A Journey Through Clean Architecture With Adapter Pattern in ASP.NET Core

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook