Coder Social home page Coder Social logo

mocknet's Introduction

.NET Core License: MIT NuGet

Theorem.MockNet.Http

The package provides a friendly mocking framework to unit test the System.Net.Http namespace.

Main features:

  • works with any .NET unit testing and mocking library
  • convenient syntax to setup http request and response messages and their properties
  • ability to validate request headers and content matching with lambda predicates
  • ability to specify status code, headers and content to be returned from the HTTP call

Examples:

A quick and simple example. This sample mocks a call to the https://jsonplaceholder.typeicode.com/todos/1 resource to return a status code of 201 Created.

using Theorem.MockNet.Http;
using Xunit; // used here for Asserting.

...

var mock = new MockHttpClient();

mock.SetupGet("/todos/1").ReturnsAsync(201);

var result = await mock.Object.GetAsync("/todos/1");

Assert.Equal(201, (int)result.StatusCode);

Lets look at a more real life example:

Todo model

public class Todo
{
    public int UserId { get; set; }
    public int Id { get; set; }
    public string Title { get; set; }
    public bool Completed { get; set; }

    public Todo() {}

    public Todo(int id, int userId, string title) => (Id, UserId, Title) = (id, userId, title);
}

Service to consume the api

using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SystemStringContent = System.Net.Http.StringContent;

public class TodoService
{
    private HttpClient _httpClient;

    public TodoService(HttpClient httpClient)
    {
        this._httpClient = httpClient;
    }

    public Task<Todo> GetAsync(int id) => GetAsync($"/todos/{id}");

    private async Task<Todo> GetAsync(string requestUri)
    {
        var response = await this._httpClient.GetAsync(requestUri);

        if (!response.IsSuccessStatusCode)
        {
            throw new Exception("Todo not found");
        }

        var json = await response.Content.ReadAsStringAsync();

        return JsonConvert.DeserializeObject<Todo>(json);
    }

    public async Task<Todo> CreateAsync(Todo todo)
    {
        var content = new SystemStringContent(JsonConvert.SerializeObject(todo), Encoding.UTF8, "application/json");

        var response = await this._httpClient.PostAsync("/todos", content);

        if (response.IsSuccessStatusCode &&
            response.Headers.Location is Uri uri)
        {
            return await GetAsync(uri.ToString());
        }

        throw new Exception("Error creating todo");
    }
}

And the unit test for the TodoService

using Theorem.MockNet.Http;
using Xunit;

public class TodoServiceTests
{
    private TodoService service;
    private MockHttpClient mock;

    public TodoServiceTests()
    {
        this.mock = new MockHttpClient();
        this.service = new TodoService(mock.Object);
    }

    [Fact]
    public async Task GetAsync_should_return_expected_todo()
    {
        var expected = new Todo(1, 456, "go shopping");

        mock.SetupGet("/todos/1").ReturnsAsync(201, expected);

        var actual = await service.GetAsync(1);

        Assert.Equal(expected.Id, actual.Id);
        Assert.Equal(expected.UserId, actual.UserId);
        Assert.Equal(expected.Title, actual.Title);
        Assert.Equal(expected.Completed, actual.Completed);
    }

    [Fact]
    public async Task GetAsync_should_throw_exception_when_invalid_status_code_is_returned()
    {
        mock.SetupGet("/todos/1").ReturnsAsync(404);

        await Assert.ThrowsAsync<Exception>(() => service.GetAsync(1));
    }

    [Fact]
    public async Task CreateAsync_should_create_and_return_new_todo()
    {
        var todo = new Todo(0, 456, "go shopping");
        var expected = new Todo(2, 456, "go shopping");

        var responseHeaders = new HttpResponseHeaders();
        responseHeaders.Add("Location", $"https://localhost/todos/{expected.Id}");

        // pass lambda predicate to validate request content
        // response from the HTTP Post method will have 201 status code and Location header
        mock.SetupPost<Todo>("/todos",
            content: x => x.UserId == todo.UserId &&
                x.Title == todo.Title &&
                x.Completed == todo.Completed).ReturnsAsync(201, responseHeaders);

        mock.SetupGet($"/todos/{expected.Id}").ReturnsAsync(expected);

        var actual = await service.CreateAsync(todo);

        Assert.Equal(expected.Id, actual.Id);
        Assert.Equal(expected.UserId, actual.UserId);
        Assert.Equal(expected.Title, actual.Title);
        Assert.Equal(expected.Completed, actual.Completed);
    }

    [Fact]
    public async Task CreateAsync_should_throw_exception_when_invalid_status_code_is_returned()
    {
        mock.SetupPost("/todos").ReturnsAsync(404);

        await Assert.ThrowsAsync<Exception>(() => service.CreateAsync(new Todo()));
    }

    [Fact]
    public async Task CreateAsync_should_throw_exception_when_missing_location_header()
    {
        mock.SetupPost("/todos").ReturnsAsync(200);

        await Assert.ThrowsAsync<Exception>(() => service.CreateAsync(new Todo()));
    }
}

For more examples on how to test other HttpClient aspects including testing headers and content body being sent to the endpoint as well as headers being returned please see tests/MockNet.Tests/Playground.cs

About TheoremOne

TheoremOne

This software is lovingly maintained and funded by TheoremOne. At TheoremOne, we specialize in solving difficult computer science problems for startups and the enterprise.

At TheoremOne we believe in and support open source software.

TheoremOne and the TheoremOne logo are trademarks or registered trademarks of TheoremOne, LLC.

mocknet's People

Contributors

yiglas avatar gpashka avatar fsaravia avatar

Stargazers

 avatar Erick Romero avatar Mike Joyce avatar  avatar Josh Coulter avatar Leandro López avatar

Watchers

Brady Brim-DeForest avatar Lucas Nasif avatar Lautaro Orazi avatar James Cloos avatar  avatar AliD avatar Mike Joyce avatar  avatar Ignacio Gómez avatar  avatar

mocknet's Issues

Add ObjectContent

The ObjectContent will make it easier to pass the object back and forth between the server and the client.

Should match the StringContent but allow to pass a generic type

public class ObjectContent<T> : IHttpContent where T : class

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.