0.8.0
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