diff --git a/expectation.not.go b/expectation.not.go new file mode 100644 index 00000000..c33a3b47 --- /dev/null +++ b/expectation.not.go @@ -0,0 +1,67 @@ +package testkit + +import ( + "fmt" + + "github.com/dogmatiq/testkit/fact" +) + +// Not is an expectation that passes only if the given expectation fails. +func Not(expectation Expectation) Expectation { + return ¬Expectation{ + caption: fmt.Sprintf("not %s", expectation.Caption()), + expectation: expectation, + } +} + +// notExpectation is an [Expectation] that inverts another expectation. +type notExpectation struct { + caption string + expectation Expectation +} + +func (e *notExpectation) Caption() string { + return e.caption +} + +func (e *notExpectation) Predicate(s PredicateScope) (Predicate, error) { + p, err := e.expectation.Predicate(s) + if err != nil { + return nil, err + } + + return ¬Predicate{ + expectation: p, + }, nil +} + +// notPredicate is the [Predicate] implementation for [notExpectation]. +type notPredicate struct { + expectation Predicate +} + +func (p *notPredicate) Notify(f fact.Fact) { + p.expectation.Notify(f) +} + +func (p *notPredicate) Ok() bool { + return !p.expectation.Ok() +} + +func (p *notPredicate) Done() { + p.expectation.Done() +} + +func (p *notPredicate) Report(ctx ReportGenerationContext) *Report { + ctx.IsInverted = !ctx.IsInverted + + r := p.expectation.Report(ctx) + + rep := &Report{ + TreeOk: ctx.TreeOk, + Ok: p.Ok(), + Criteria: "do not " + r.Criteria, + } + + return rep +} diff --git a/expectation.not_test.go b/expectation.not_test.go new file mode 100644 index 00000000..2bcc60bb --- /dev/null +++ b/expectation.not_test.go @@ -0,0 +1,106 @@ +package testkit_test + +import ( + "context" + + "github.com/dogmatiq/dogma" + . "github.com/dogmatiq/enginekit/enginetest/stubs" + . "github.com/dogmatiq/testkit" + "github.com/dogmatiq/testkit/internal/testingmock" + g "github.com/onsi/ginkgo/v2" + gm "github.com/onsi/gomega" +) + +var _ = g.Context("not expectation", func() { + var ( + testingT *testingmock.T + app dogma.Application + test *Test + ) + + g.BeforeEach(func() { + testingT = &testingmock.T{ + FailSilently: true, + } + + app = &ApplicationStub{ + ConfigureFunc: func(c dogma.ApplicationConfigurer) { + c.Identity("", "00df8612-2fd4-4ae3-9acf-afc2b4daf272") + c.RegisterIntegration(&IntegrationMessageHandlerStub{ + ConfigureFunc: func(c dogma.IntegrationConfigurer) { + c.Identity("", "12dfb90b-e47b-4d49-b834-294b01992ad0") + c.Routes( + dogma.HandlesCommand[CommandStub[TypeA]](), + dogma.RecordsEvent[EventStub[TypeA]](), + ) + }, + HandleCommandFunc: func( + _ context.Context, + s dogma.IntegrationCommandScope, + _ dogma.Command, + ) error { + s.RecordEvent(EventA1) + return nil + }, + }) + }, + } + + test = Begin(testingT, app). + EnableHandlers("") + }) + + testExpectationBehavior := func( + e Expectation, + ok bool, + rm reportMatcher, + ) { + test.Expect(ExecuteCommand(CommandA1), e) + rm(testingT) + gm.Expect(testingT.Failed()).To(gm.Equal(!ok)) + } + + g.Describe("func Not()", func() { + g.DescribeTable( + "expectation behavior", + testExpectationBehavior, + g.Entry( + "it fails when the child expectation passes", + Not(ToRecordEvent(EventA1)), + expectFail, + expectReport( + `✗ do not record a specific 'stubs.EventStub[TypeA]' event`, + ), + ), + g.Entry( + "it passes when the child expectation fails", + Not(ToRecordEvent(EventA2)), + expectPass, + expectReport( + `✓ do not record a specific 'stubs.EventStub[TypeA]' event`, + ), + ), + ) + + g.It("produces the expected caption", func() { + test.Expect( + noop, + Not(ToRecordEvent(EventA2)), + ) + + gm.Expect(testingT.Logs).To(gm.ContainElement( + "--- expect [no-op] not to record a specific 'stubs.EventStub[TypeA]' event ---", + )) + }) + + g.It("fails the test if the child cannot construct a predicate", func() { + test.Expect( + noop, + Not(failBeforeAction), + ) + + gm.Expect(testingT.Logs).To(gm.ContainElement("")) + gm.Expect(testingT.Failed()).To(gm.BeTrue()) + }) + }) +}) diff --git a/internal/fixtures/protobuf.pb.go b/internal/fixtures/protobuf.pb.go index 93d0ea24..40cdbdb9 100644 --- a/internal/fixtures/protobuf.pb.go +++ b/internal/fixtures/protobuf.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.2 +// protoc v5.27.3 // source: github.com/dogmatiq/testkit/internal/fixtures/protobuf.proto package fixtures