2

For my tests purpose, I would like to mock random numbers in Go. So I created the Random interface. During unit testing I return the identity function, while for implementation I generate a random number with rand package.

It is the right way for mocking random numbers in Go? Any help appreciated.

Go Playground: https://play.golang.org/p/bibNnmY2t1g

main:

package main import ( "time" "math/rand" "fmt" ) func init() { rand.Seed(time.Now().UnixNano()) } type Random interface { Uint(_ uint) uint } type rndGenerator func(n uint) uint type Randomizer struct { fn rndGenerator } func NewRandomizer(fn rndGenerator) *Randomizer { return &Randomizer{fn: fn} } func (r *Randomizer) Uint(n uint) uint { return r.fn(n) } func fakeRand(n uint) uint { return n } func realRand(_ uint) uint { return uint(rand.Uint64()) } func main() { fakeRnd := NewRandomizer(fakeRand).Uint fmt.Println(fakeRnd(1)) fmt.Println(fakeRnd(2)) realRnd := NewRandomizer(realRand).Uint fmt.Println(realRnd(0)) fmt.Println(realRnd(0)) } 

test:

package main import ( "testing" "math/rand" "reflect" ) func TestNewRandomizer(t *testing.T) { fn := func(n uint) uint { return n } type args struct { fn rndGenerator } tests := []struct { name string args args want *Randomizer }{ { "test", args{fn}, &Randomizer{fn}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := NewRandomizer(tt.args.fn); reflect.TypeOf(got) != reflect.TypeOf(tt.want) { t.Errorf("NewRandomizer() = %v, want %v", got, tt.want) } }) } } func TestRandomer_Uint(t *testing.T) { rnd := uint(rand.Uint64()) type fields struct { fn rndGenerator } type args struct { n uint } tests := []struct { name string fields fields args args want uint }{ { "test", fields{func(n uint) uint { return n }}, args{rnd}, rnd, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &Randomizer{ fn: tt.fields.fn, } if got := r.Uint(tt.args.n); got != tt.want { t.Errorf("Randomizer.Uint() = %v, want %v", got, tt.want) } }) } } func Test_fakeRand(t *testing.T) { rnd := uint(rand.Uint64()) type args struct { n uint } tests := []struct { name string args args want uint }{ { "test", args{rnd}, rnd, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := fakeRand(tt.args.n); got != tt.want { t.Errorf("fakeRand() = %v, want %v", got, tt.want) } }) } } func Test_realRand(t *testing.T) { type args struct { in0 uint } tests := []struct { name string args args want bool }{ { "test", args{0}, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := realRand(tt.args.in0); got < 1 { t.Errorf("realRand() = %v, want %v", got, tt.want) } }) } } 
7
  • What is the purpose of mocking random numbers? (Changing underlying generator? Using third party packages? Or getting a specific sequence for tests?) Commented Jan 20, 2018 at 19:47
  • It's for testing purpose. All my structs have a random id in the implementation. Commented Jan 20, 2018 at 19:53
  • 1
    One of the reasons for using a PRNG is so that the output is repeatable. What is the goal of the mock if the output is already the same each time? Commented Jan 20, 2018 at 19:53
  • IT's not the same since I use an identity function where I pass as argument the output I want Commented Jan 20, 2018 at 19:56
  • See Go Playground for output examples: play.golang.org/p/bibNnmY2t1g Commented Jan 20, 2018 at 20:02

2 Answers 2

0

Your example makes no real use of the Random interface since the mocking is being done at the function field level of the Randomizer type.

I would recommend, if possible, to ditch the function field as well as the functions and instead define two separate implementations of the Random interface. You can use empty structs for this, they may look weird at first but they have the nice property of taking up 0 bytes of memory.

The main reason for the recommendation is that when you use function fields you lose the ability to compare your struct type values with reflect.DeepEqual. This is because two function values are only equal if they have the same type and both are nil.

As an example let's first look at your TestNewRandomizer which is a symptom of the issue stated above. In the test you're just comparing the type of the return value which is something already ensured by the compiler, so the test is absolutely pointless.

Now, let's say you drop the test, since it's useless, but for some reason you keep the function field. Because of this any struct type that depends on *Randomizer will also be untestable with DeepEqual and you'll have the same difficulties when trying to come up with a test for that type.

package main import ( "time" "math/rand" "fmt" ) func init() { rand.Seed(time.Now().UnixNano()) } type Random interface { Uint(_ uint) uint } type Randomizer struct {} func NewRandomizer() Randomizer { return Randomizer{} } func (r Randomizer) Uint(n uint) uint { return uint(rand.Uint64()) } type FakeRandomizer struct {} func NewFakeRandomizer() FakeRandomizer { return FakeRandomizer{} } func (r FakeRandomizer) Uint(n uint) uint { return n } func main() { fakeRnd := NewFakeRandomizer().Uint fmt.Println(fakeRnd(1)) fmt.Println(fakeRnd(2)) realRnd := NewRandomizer().Uint fmt.Println(realRnd(0)) fmt.Println(realRnd(0)) } 

Note that i'm intentionally returning values instead of pointers since empty structs are smaller than pointers.

Sign up to request clarification or add additional context in comments.

3 Comments

Many Thanks. I borrowed your code and changed the Random method signature to get rid of the function argument: play.golang.org/p/w2iKC1Dz0Ek .
@SofianeCherchalli it's better to have your StudentManager depend on the interface Random instead of the concrete type Randomizer, that way when you write tests for NewStudent you can mock rnd. play.golang.org/p/vibEizUH7Bg
Sorry it was a typo error at midnight. I meant Random :)
0

I have a method which generates a random integer and returns true if the integer is less than or equal to 50 and false if the integer is greater than 50 in the range [0, 100).

This is how I have created my structures to mock the functionality:

type Decider struct { RandImpl func(int) int } func (d *Decider) Decide(randRange int) bool { randVal := d.RandImpl(randRange) log.Info("RandVal: ", randVal) if randVal <= 50 { return true } return false } 

I'm calling this method in this manner:

rand.Seed(time.Now().UnixNano()) decider := decide.Decider{ RandImpl: func(x int) int { return rand.Intn(x) }, } decider.Decide(100) 

In my _test.go file, I have this:

decider := Decider{ RandImpl: func(x int) int { return 42 }, } ret := decider.Decide(100) assert.True(t, ret) 

This way you can mock the functionality of the random number generator.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.