Skip to content

Releases: oskardudycz/Ogooreck

0.8.2

24 May 06:09
Compare
Choose a tag to compare

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 #25
  • Added possibility to inject ILoggerProvider to enable injecting XUnit test output helper. by @oskardudycz in #25
    You can do it now like for XUnit:
    ApiSpecification<TProgram>.With(new XUnitLoggerProvider(testOutputHelper, appendScope: false)

Full Changelog: 0.8.1...0.8.2

0.8.1

03 May 06:39
Compare
Choose a tag to compare

What's Changed

Full Changelog: 0.8.0...0.8.1

0.8.0

15 Aug 13:28
Compare
Choose a tag to compare

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: oskardudycz/EventSourcing.NetCore#222.

See details in Pull Request: #17

0.7.0

23 Jul 15:23
Compare
Choose a tag to compare

Added support for .NET 6 and bumped dependencies.

Full Changelog: 0.6.0...0.7.0

.NET 7 upgrade and F# samples

17 Nov 09:05
Compare
Choose a tag to compare

What's Changed

Full Changelog: 0.5.1...0.6.0

Default entity initialisation improvements

08 Oct 08:25
Compare
Choose a tag to compare

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

Added support for business logic tests

07 Oct 16:00
7d8bfa4
Compare
Choose a tag to compare

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 #9, #10

Full Changelog: 0.3.0...0.5.0

Ogooreck 0.3.0

30 Jun 12:07
Compare
Choose a tag to compare

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 0.2.1

28 Jun 11:19
Compare
Choose a tag to compare

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 0.2.0

28 Jun 09:27
Compare
Choose a tag to compare

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: #5