2

I use testify (v1.6.1) and need to test if methods of interfaces call in right order. I checked the documentation and tried to find any information in the internet, but didn't find anything about mocks order checking.

Example:

type InterfaceA interface {
    Execute()
}

type InterfaceB interface {
    Execute()
}
type Composition struct {
    a InterfaceA
    b InterfaceB
}

func (c * Composition) Apply() error {
    //How to check that "a" execute before "b"?
    c.a.Execute()
    c.b.Execute()
    return nil
}
kozmo
  • 4,024
  • 3
  • 30
  • 48

4 Answers4

2

This is not directly supported, even though there is an opened issue (stretchr/testify/issue 741 "assert mock calls in order")

The more general issue 684 "Assert call order?" includes the rebuttal:

IMO you should check the output of the function but not how it works internally. It may lead to testing implementation which is very hard to maintain.

Although, in the same thread:

IMO there are cases to enforce order. i.e if you mock a mutex, you better check that Lock was always called before Unlock.
We can have a simple implementation where the mock has an "assertExpectationsInOrder" true/false flag that can be set before any expectations are added.

This can lead to some test like cassandra-operator/cmd/operator/controller_test.go which records events to test their orders.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
2

Following VonC answer, a pull request was merged to the relevant issue https://github.com/stretchr/testify/pull/1106.

This is now possible with:

call1 := mockInterfaceA.On("Execute").Return(nil)
call2 := mockInterfaceB.On("Execute").Return(nil).NotBefore(call1)

comp := Composition{mockInterfaceA, mockInterfaceB}
comp.Apply()

In case they are not called in the expected order, the test will panic with a relevant message

panic: mock: Unexpected Method Call
-----------------------------

Apply()
        0: ...

Must not be called before:

Execute()
Chen A.
  • 10,140
  • 3
  • 42
  • 61
1

IMO there are cases to enforce order. i.e if you mock a mutex, you better check that Lock was always called before Unlock.

It's a simple single thread realization:

func TestOrderOfMocks(t *testing.T) {
    order := 0
    amock := new(AMock)
    amock.On("Execute").Run(func(args mock.Arguments) {
        if order++; order != 1 {
            t.Fail()
        }
    })

    bmock := new(BMock)
    bmock.On("Execute").Run(func(args mock.Arguments) {
        if order++; order != 2 {
            t.Fail()
        }
    })

    c := &Composition{amock, bmock}
    err := c.Apply()
    require.NoError(t, err)
}

PLAYGROUND

if there are reasons, you can complicate the order checking logic...

kozmo
  • 4,024
  • 3
  • 30
  • 48
0

As others have said this is an internal detail and does entangle your testing with the implementation. This means nothing if you have determined that the order matters. Keeping things succinct here is another solution using the bare minimum for testing order.

Create two spies that implement InterfaceA and InterfaceB.

type SpyA struct {
    Calls *[]string
}

func (s *SpyA) Execute() {
    *s.Calls = append(*s.Calls, "ExecuteA")
}

type SpyB struct {
    Calls *[]string
}

func (s *SpyB) Execute() {
    *s.Calls = append(*s.Calls, "ExecuteB")
}

Then use them like this.

func TestApply(t *testing.T) {
    got := []string{}
    c := &Composition{
        a: &SpyA{&got},
        b: &SpyB{&got},
    }

    c.Apply()

    expected := []string{"ExecuteA", "ExecuteB"}

    if len(got) != len(expected) {
        t.Fail()
    }

    for i := range got {
        if got[i] != expected[i] {
            t.Fail()
        }
    }
}

Playground

Steven Eckhoff
  • 992
  • 9
  • 18