Ogooreck

Sneaky Testing Library in BDD style

MIT License

Stars
124
Committers
1

Bot releases are visible (Hide)

Ogooreck - 0.8.2

Published by oskardudycz 5 months ago

What's Changed

  • Made WebApplicationFactory disposal silent In case there's some dependency (like Marten ProjectCoordinator atm) that sometimes fails during disposal. That should not make tests fail, even though it's not a great thing. by @oskardudycz in https://github.com/oskardudycz/Ogooreck/pull/25
  • Added possibility to inject ILoggerProvider to enable injecting XUnit test output helper. by @oskardudycz in https://github.com/oskardudycz/Ogooreck/pull/25
    You can do it now like for XUnit:
    ApiSpecification<TProgram>.With(new XUnitLoggerProvider(testOutputHelper, appendScope: false)
    

Full Changelog: https://github.com/oskardudycz/Ogooreck/compare/0.8.1...0.8.2

Ogooreck - 0.8.1 Latest Release

Published by oskardudycz 6 months ago

What's Changed

Full Changelog: https://github.com/oskardudycz/Ogooreck/compare/0.8.0...0.8.1

Ogooreck - 0.8.0

Published by oskardudycz about 1 year ago

Refactored API Specification syntax to enable easier test setup.

To do that, it was necessary to perform breaking changes and move the request builder from Given to When. Still, accidentally it also improved the test clarity, as too often it just had empty Given.

Consolidation of the request building also enabled more improvements like:

  • easier test setup by just passing the request definition without the need for the dedicated fixtures,
  • using values from the response results to shape the url or in assertions (e.g. using created id from location header),
  • adding a description to given and then.

I also introduced test context that may eventually be used for building advanced reporters - e.g. markdown documentation.

Migration:

Move request builders from Given to Then. Previous:

public Task GET_ReturnsShoppingCartDetails() =>
    API.Given(
            URI($"/api/ShoppingCarts/{API.ShoppingCartId}")
        )
        .When(GET_UNTIL(RESPONSE_SUCCEEDED))
        .Then(
            OK,
            RESPONSE_BODY(new ShoppingCartDetails
            {
                Id = API.ShoppingCartId,
                Status = ShoppingCartStatus.Confirmed,
                ProductItems = new List<PricedProductItem>(),
                ClientId = API.ClientId,
                Version = 2,
            }));

Now:

public Task GET_ReturnsShoppingCartDetails() =>
    API.Given()
        .When(GET, URI($"/api/ShoppingCarts/{API.ShoppingCartId}"))
        .Until(RESPONSE_SUCCEEDED)
        .Then(
            OK,
            RESPONSE_BODY(new ShoppingCartDetails
            {
                Id = API.ShoppingCartId,
                Status = ShoppingCartStatus.Confirmed,
                ProductItems = new List<PricedProductItem>(),
                ClientId = API.ClientId,
                Version = 2,
            }));

Also, you can change your advanced setup from (using XUnit):

using Carts.Api.Requests;
using Carts.ShoppingCarts;
using Carts.ShoppingCarts.GettingCartById;
using Carts.ShoppingCarts.Products;
using FluentAssertions;
using Ogooreck.API;
using static Ogooreck.API.ApiSpecification;
using Xunit;

namespace Carts.Api.Tests.ShoppingCarts.AddingProduct;

public class AddProductFixture: ApiSpecification<Program>, IAsyncLifetime
{
    public Guid ShoppingCartId { get; private set; }

    public readonly Guid ClientId = Guid.NewGuid();

    public async Task InitializeAsync()
    {
        var openResponse = await Send(
            new ApiRequest(POST, URI("/api/ShoppingCarts"), BODY(new OpenShoppingCartRequest(ClientId)))
        );

        await CREATED(openResponse);
        await RESPONSE_LOCATION_HEADER()(openResponse);

        ShoppingCartId = openResponse.GetCreatedId<Guid>();
    }

    public Task DisposeAsync() => Task.CompletedTask;
}

public class AddProductTests: IClassFixture<AddProductFixture>
{
    private readonly AddProductFixture API;

    public AddProductTests(AddProductFixture api) => API = api;

    [Fact]
    [Trait("Category", "Acceptance")]
    public async Task Post_Should_AddProductItem_To_ShoppingCart()
    {
        var product = new ProductItemRequest(Guid.NewGuid(), 1);

        await API
            .Given(
                URI($"/api/ShoppingCarts/{API.ShoppingCartId}/products"),
                BODY(new AddProductRequest(product)),
                HEADERS(IF_MATCH(1))
            )
            .When(POST)
            .Then(OK);

        await API
            .Given(URI($"/api/ShoppingCarts/{API.ShoppingCartId}"))
            .When(GET_UNTIL(RESPONSE_ETAG_IS(2)))
            .Then(
                RESPONSE_BODY<ShoppingCartDetails>(details =>
                {
                    details.Id.Should().Be(API.ShoppingCartId);
                    details.Status.Should().Be(ShoppingCartStatus.Pending);
                    details.ProductItems.Should().HaveCount(1);
                    details.ProductItems.Single().ProductItem.Should()
                        .Be(ProductItem.From(product.ProductId, product.Quantity));
                    details.Version.Should().Be(2);
                })
            );
    }
}

to

using Carts.Api.Requests;
using Carts.ShoppingCarts;
using Carts.ShoppingCarts.GettingCartById;
using FluentAssertions;
using Ogooreck.API;
using Xunit;
using static Ogooreck.API.ApiSpecification;

namespace Carts.Api.Tests.ShoppingCarts.AddingProduct;

public class AddProductTests: IClassFixture<ApiSpecification<Program>>
{
    private readonly ApiSpecification<Program> API;

    public AddProductTests(ApiSpecification<Program> api) => API = api;

    private readonly ProductItemRequest product = new(Guid.NewGuid(), 1);

    [Fact]
    [Trait("Category", "Acceptance")]
    public Task Post_Should_AddProductItem_To_ShoppingCart() =>
        API.Given("Opened Shopping Cart", OpenShoppingCart())
            .When(
                "Add new product",
                POST,
                URI(ctx => $"/api/ShoppingCarts/{ctx.GetCreatedId()}/products"),
                BODY(new AddProductRequest(product)),
                HEADERS(IF_MATCH(1))
            )
            .Then(OK)
            .And()
            .When(
                "Get updated shopping cart details",
                GET,
                URI(ctx => $"/api/ShoppingCarts/{ctx.GetCreatedId()}")
            )
            .Then(
                RESPONSE_BODY<ShoppingCartDetails>((details, ctx) =>
                {
                    details.Id.Should().Be(ctx.GetCreatedId());
                    details.Status.Should().Be(ShoppingCartStatus.Pending);
                    var productItem = details.ProductItems.Single();
                    productItem.Quantity.Should().Be(product.Quantity);
                    productItem.ProductId.Should().Be(product.ProductId!.Value);
                    details.Version.Should().Be(2);
                })
            );

    public static RequestDefinition OpenShoppingCart(Guid? clientId = null) =>
        SEND(
            "Open ShoppingCart",
            POST,
            URI("/api/ShoppingCarts"),
            BODY(new OpenShoppingCartRequest(clientId ?? Guid.NewGuid()))
        );

}

See also example migration from the old syntax: https://github.com/oskardudycz/EventSourcing.NetCore/pull/222.

See details in Pull Request: https://github.com/oskardudycz/Ogooreck/pull/17

Ogooreck - 0.7.0

Published by oskardudycz about 1 year ago

Added support for .NET 6 and bumped dependencies.

Full Changelog: https://github.com/oskardudycz/Ogooreck/compare/0.6.0...0.7.0

Ogooreck - .NET 7 upgrade and F# samples

Published by oskardudycz almost 2 years ago

What's Changed

Full Changelog: https://github.com/oskardudycz/Ogooreck/compare/0.5.1...0.6.0

Ogooreck - Default entity initialisation improvements

Published by oskardudycz about 2 years ago

Updated ObjectFactory implementation to use Expression with memoization to create the new object instead of Activator. It removes the reflection magic and enhances performance.

Ogooreck - Added support for business logic tests

Published by oskardudycz about 2 years ago

What's Changed

Added business logic specifications to allow testing:

  • Deciders,
  • Command handlers,
  • Event Sourced entities./aggregates,
  • State-based entities/aggregates.

See the documentation: https://github.com/oskardudycz/Ogooreck/tree/main#business-logic-testing.

by @oskardudycz in https://github.com/oskardudycz/Ogooreck/pull/9, https://github.com/oskardudycz/Ogooreck/pull/10

Full Changelog: https://github.com/oskardudycz/Ogooreck/compare/0.3.0...0.5.0

Ogooreck - Ogooreck 0.3.0

Published by oskardudycz over 2 years ago

Added a few helper methods:

  • helper for getting the ETag value from HTTPResponse
  • a new overload to And method to allow easier chaining and transformations.
Ogooreck - Ogooreck 0.2.1

Published by oskardudycz over 2 years ago

Updated GetCreatedId logic to not use Request URI

This dependency was redundant and fault, as returned location header URI may be different and not start with the request URI.

Ogooreck - Ogooreck 0.2.0

Published by oskardudycz over 2 years ago

Broke down the CREATED status with Location Header checks to give the possibility of providing a custom header easier.

Response Location Header

Now instead of just using the CREATED method:

public Task RegisterProduct() =>
        API.Given(
                URI("/api/products"),
                BODY(new RegisterProductRequest("abc-123", "Ogooreck"))
            )
            .When(POST)
            .Then(CREATED);

You need to use also RESPONSE_LOCATION_HEADER.

public Task RegisterProduct() =>
        API.Given(
                URI("/api/products"),
                BODY(new RegisterProductRequest("abc-123", "Ogooreck"))
            )
            .When(POST)
            .Then(CREATED, RESPONSE_LOCATION_HEADER());

By default, it will check if the prefix matches the Request URI, but you can provide your prefix, e.g.:

.Then(CREATED, RESPONSE_LOCATION_HEADER("/api/with/your/custom/prefix/"));

You can also use the location header check separately from the CREATED helper.

Response ETag Header

Added also RESPONSE_ETAG_HEADER, you can use it as:

public Task RegisterProduct() =>
        API.Given(
                URI("/api/products"),
                BODY(new RegisterProductRequest("abc-123", "Ogooreck"))
            )
            .When(POST)
            .Then(CREATED, RESPONSE_ETAG_HEADER(1));

It supports both weak and strong ETag format, by default using the weak one.

Other Response Headers

Added also RESPONSE_HEADERS helper and aligned response headers check with it.

See the details in PR: https://github.com/oskardudycz/Ogooreck/pull/5

Ogooreck - Ogooreck 0.1.1

Published by oskardudycz over 2 years ago

It's the first working version of the Ogooreck. Ogooreck is a Sneaky Test library. It helps to write readable and self-documenting tests. Current available for API testing.

Main assumptions:

  • write tests seamlessly,
  • make them readable,
  • cut needed boilerplate by the set of helpful extensions and wrappers,
  • don't create a full-blown BDD framework,
  • no Domain-Specific Language,
  • don't replace testing frameworks (works with all, so XUnit, NUnit, MSTests, etc.),
  • testing frameworks and assert library agnostic,
  • keep things simple, but allow compositions and extension.

Links