Skip to content

Commit ecdcb98

Browse files
committed
fake: add package fake for mocks (+add example)
Not called mock because github.com/stretchr/testify/mock is likely to be used along this packge (name conflict)
1 parent 39d922b commit ecdcb98

File tree

10 files changed

+629
-0
lines changed

10 files changed

+629
-0
lines changed

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ Here you can find a overview of examples on how to use demoinfocs-golang.
99
|[entities](entities)|Using unhandled data from entities (`Parser.ServerClasses()`)|
1010
|[net-messages](net-messages)|Parsing and handling custom net-messages|
1111
|[print-events](print-events)|Printing kills, scores & chat messages|
12+
|[mocking](mocking)|Using the `fake` package to write unit tests for your code|

examples/mocking/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Mocking the parser
2+
3+
This example shows you how to use the provided [`fake` package](https://godoc.org/github.com/markus-wa/demoinfocs-golang/fake) to mock `demoinfocs.IParser` and other parts of the library.
4+
That way you will be able to write useful unit tests for your application.
5+
6+
## System under test
7+
8+
First, let's have a look at the API of our code, the 'system under test':
9+
10+
```go
11+
import (
12+
dem "github.com/markus-wa/demoinfocs-golang"
13+
events "github.com/markus-wa/demoinfocs-golang/events"
14+
)
15+
16+
func collectKills(parser dem.IParser) (kills []events.Kill, err error) {
17+
...
18+
}
19+
```
20+
21+
We deliberately ignore the implementation so we don't make assumptions about the code since it might change in the future.
22+
23+
As you can see `collectKills` takes an `IParser` as input and returns a slice of `events.Kill` and potentially an error.
24+
25+
## Positive test case
26+
27+
Now let's have a look at our first test. Here we want to ensure that all kills are collected and that the order of the collected events is correct.
28+
29+
```go
30+
import (
31+
"errors"
32+
"testing"
33+
34+
assert "github.com/stretchr/testify/assert"
35+
36+
common "github.com/markus-wa/demoinfocs-golang/common"
37+
events "github.com/markus-wa/demoinfocs-golang/events"
38+
fake "github.com/markus-wa/demoinfocs-golang/fake"
39+
)
40+
41+
func TestCollectKills(t *testing.T) {
42+
parser := fake.NewParser()
43+
kill1 := kill(common.EqAK47)
44+
kill2 := kill(common.EqScout)
45+
kill3 := kill(common.EqAUG)
46+
parser.MockEvents(kill1) // First frame
47+
parser.MockEvents(kill2, kill3) // Second frame
48+
49+
parser.On("ParseToEnd").Return(nil) // Return no error
50+
51+
actual, err := collectKills(parser)
52+
53+
assert.Nil(t, err)
54+
expected := []events.Kill{kill1, kill2, kill3}
55+
assert.Equal(t, expected, actual)
56+
}
57+
58+
func kill(wep common.EquipmentElement) events.Kill {
59+
eq := common.NewEquipment(wep)
60+
return events.Kill{
61+
Killer: new(common.Player),
62+
Weapon: &eq,
63+
Victim: new(common.Player),
64+
}
65+
}
66+
```
67+
68+
As you can see we first create a mocked parser with `fake.NewParser()`.
69+
70+
Then we create two `Kill` events and add them into the `Parser.Events` map.
71+
The map index indicates at which frame the events will be sent out, in our case that's during the first and second frame, as we just iterate over the slice indices.
72+
73+
Note: Especially when used together with `Parser.NetMessages` it can be useful to set these indices manually to ensure the events and net-messages are sent at the right moment.
74+
75+
## Negative test case
76+
77+
Last but not least we want to do another test that ensures any error the parser encounters is returned to the callee and not suppressed by our function.
78+
79+
```go
80+
import (
81+
"errors"
82+
"testing"
83+
84+
assert "github.com/stretchr/testify/assert"
85+
86+
common "github.com/markus-wa/demoinfocs-golang/common"
87+
events "github.com/markus-wa/demoinfocs-golang/events"
88+
fake "github.com/markus-wa/demoinfocs-golang/fake"
89+
)
90+
91+
func TestCollectKillsError(t *testing.T) {
92+
parser := fake.NewParser()
93+
expectedErr := errors.New("Test error")
94+
parser.On("ParseToEnd").Return(expectedErr)
95+
96+
kills, actualErr := collectKills(parser)
97+
98+
assert.Equal(t, expectedErr, actualErr)
99+
assert.Nil(t, kills)
100+
}
101+
```
102+
103+
This test simply tells the mock to return the specified error and asserts that our function returns it to us.
104+
It also makes sure that kills is nil, and not an empty slice.

examples/mocking/collect_kills.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package mocking
2+
3+
import (
4+
dem "github.com/markus-wa/demoinfocs-golang"
5+
events "github.com/markus-wa/demoinfocs-golang/events"
6+
)
7+
8+
func collectKills(parser dem.IParser) (kills []events.Kill, err error) {
9+
parser.RegisterEventHandler(func(kill events.Kill) {
10+
kills = append(kills, kill)
11+
})
12+
err = parser.ParseToEnd()
13+
return
14+
}

examples/mocking/mocking_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package mocking
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
assert "github.com/stretchr/testify/assert"
8+
9+
common "github.com/markus-wa/demoinfocs-golang/common"
10+
events "github.com/markus-wa/demoinfocs-golang/events"
11+
fake "github.com/markus-wa/demoinfocs-golang/fake"
12+
)
13+
14+
func TestCollectKills(t *testing.T) {
15+
parser := fake.NewParser()
16+
kill1 := kill(common.EqAK47)
17+
kill2 := kill(common.EqScout)
18+
kill3 := kill(common.EqAUG)
19+
parser.MockEvents(kill1) // First frame
20+
parser.MockEvents(kill2, kill3) // Second frame
21+
22+
parser.On("ParseToEnd").Return(nil) // Return no error
23+
24+
actual, err := collectKills(parser)
25+
26+
assert.Nil(t, err)
27+
expected := []events.Kill{kill1, kill2, kill3}
28+
assert.Equal(t, expected, actual)
29+
}
30+
31+
func kill(wep common.EquipmentElement) events.Kill {
32+
eq := common.NewEquipment(wep)
33+
return events.Kill{
34+
Killer: new(common.Player),
35+
Weapon: &eq,
36+
Victim: new(common.Player),
37+
}
38+
}
39+
40+
func TestCollectKillsError(t *testing.T) {
41+
parser := fake.NewParser()
42+
expectedErr := errors.New("Test error")
43+
parser.On("ParseToEnd").Return(expectedErr)
44+
45+
kills, actualErr := collectKills(parser)
46+
47+
assert.Nil(t, kills)
48+
assert.Equal(t, expectedErr, actualErr)
49+
}

fake/game_state.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package fake
2+
3+
import (
4+
mock "github.com/stretchr/testify/mock"
5+
6+
dem "github.com/markus-wa/demoinfocs-golang"
7+
common "github.com/markus-wa/demoinfocs-golang/common"
8+
st "github.com/markus-wa/demoinfocs-golang/sendtables"
9+
)
10+
11+
// GameState is a mock for of demoinfocs.IGameState.
12+
type GameState struct {
13+
mock.Mock
14+
}
15+
16+
// IngameTick is a mock-implementation of IGameState.IngameTick().
17+
func (gs *GameState) IngameTick() int {
18+
return gs.Called().Int(0)
19+
}
20+
21+
// TeamCounterTerrorists is a mock-implementation of IGameState.TeamCounterTerrorists().
22+
func (gs *GameState) TeamCounterTerrorists() *common.TeamState {
23+
return gs.Called().Get(0).(*common.TeamState)
24+
}
25+
26+
// TeamTerrorists is a mock-implementation of IGameState.TeamTerrorists().
27+
func (gs *GameState) TeamTerrorists() *common.TeamState {
28+
return gs.Called().Get(0).(*common.TeamState)
29+
}
30+
31+
// Participants is a mock-implementation of IGameState.Participants().
32+
func (gs *GameState) Participants() dem.IParticipants {
33+
return gs.Called().Get(0).(dem.IParticipants)
34+
}
35+
36+
// GrenadeProjectiles is a mock-implementation of IGameState.GrenadeProjectiles().
37+
func (gs *GameState) GrenadeProjectiles() map[int]*common.GrenadeProjectile {
38+
return gs.Called().Get(0).(map[int]*common.GrenadeProjectile)
39+
}
40+
41+
// Infernos is a mock-implementation of IGameState.Infernos().
42+
func (gs *GameState) Infernos() map[int]*common.Inferno {
43+
return gs.Called().Get(0).(map[int]*common.Inferno)
44+
}
45+
46+
// Entities is a mock-implementation of IGameState.Entities().
47+
func (gs *GameState) Entities() map[int]*st.Entity {
48+
return gs.Called().Get(0).(map[int]*st.Entity)
49+
}
50+
51+
// Bomb is a mock-implementation of IGameState.Bomb().
52+
func (gs *GameState) Bomb() *common.Bomb {
53+
return gs.Called().Get(0).(*common.Bomb)
54+
}
55+
56+
// TotalRoundsPlayed is a mock-implementation of IGameState.TotalRoundsPlayed().
57+
func (gs *GameState) TotalRoundsPlayed() int {
58+
return gs.Called().Int(0)
59+
}
60+
61+
// IsWarmupPeriod is a mock-implementation of IGameState.IsWarmupPeriod().
62+
func (gs *GameState) IsWarmupPeriod() bool {
63+
return gs.Called().Bool(0)
64+
}
65+
66+
// IsMatchStarted is a mock-implementation of IGameState.IsMatchStarted().
67+
func (gs *GameState) IsMatchStarted() bool {
68+
return gs.Called().Bool(0)
69+
}

0 commit comments

Comments
 (0)