Testing using mock functionality

278 views
Skip to first unread message

wylde...@gmail.com

unread,
Jul 22, 2019, 1:10:59 AM7/22/19
to golang-nuts
Hello,

I'm new to go (and new to unit testing) and have a question about how to test my code. I have a package that uses a library, with a kinda big interface, let's call it A. I don't use all the functions in A, but I want to mock the functionality that I do use from this interface for my unit tests. So I made my own interface that is a subset of the big interface, let's call it B.

The problem is that I would also like to test the construction of the interface. So I made a constructor type that returns B. But I cannot pass the real A constructor function into my package in place of the mock B constructor!

Here's an example

package main


// A is an interface that does X, Y and Z
type A
interface {
  X
()
  Y
()
  Z
()
}

// B is a strict subset of A.
type B 
interface {
  X
()
  Y
()
}

// AConstructor claims to build an interface that does A
func
AConstructor() A {
  
return nil
}

// BConstructorType is a type of function that returns a B interface
type
BConstructorType func() B


// Initialize creates a B and calls the methods in the interface. (I want to be able to mock A and test this function)
func
Initialize(constructor BConstructorType) {
  b
:= constructor()
  b
.X()
  b
.Y()
}


func main
() {
  
// cannot use AConstructor (type func() A) as type BConstructorType in argument to Initialize Initialize(AConstructor)
  Initialize(AConstructor)
}

Why is AConstructor not of type BConstructorType? It seems like the type system is interpreting it's definition quite literally. Would this change the current compiler implementation a lot to support this kind of smart type checking?

I know I can use a closure to curry the constructor arguments and have an explicit return type as a workaround
func main() {
  Initialize(func() B { return AConstructor() })
}
but it feels clunky and unnecessary, and seems to be a bad way to have to call my Initialize method outside of the package

Furthermore, if I define the curried constructor as a function in my package to ease the pain, then it will give me a warning if B is not exported.
// exported func ConstructRealA returns unexported type B, which can be annoying to use
func
ConstructRealA() B {
 
return AConstructor()
}


Do y'all have any thoughts on this? Am I going about this wrong? Should I change the way I'm testing to avoid the whole constructor problem? I know generics have been considered as an addition to the language, but would generics solve this problem? 

Thanks,
Kevin

Marvin Renich

unread,
Jul 22, 2019, 7:33:03 AM7/22/19
to golang-nuts
* wylde...@gmail.com <wylde...@gmail.com> [190722 01:11]:
> I'm new to go (and new to unit testing) and have a question about how to
> test my code. I have a package that uses a library, with a kinda big
> interface <https://godoc.org/github.com/eclipse/paho.mqtt.golang>, let's
> call it A. I don't use all the functions in A, but I want to mock the
> functionality that I do use from this interface for my unit tests. So I
> made my own interface that is a subset of the big interface, let's call it B
> .
>
> The problem is that I would also like to test the construction of the
> interface. So I made a constructor type that returns B. But I cannot pass
> the real A constructor function into my package in place of the mock B
> constructor!
>
A and B are different types. B is a subset of A (and they are both
interfaces), therefore a value of type A implements B, so a value of
type A can be assigned (see Assignability[1]) to a variable of type B
without any explicit conversion (see Conversions[2]) (i.e. the
conversion is performed implicitly).

However, a function returning A is not assignable to a variable of type
function returning B, even if A is assignable to B. The functions have
different types (see Function types[3]). The rules of assignability are
applied neither individually nor recursively to each argument and result
of the function; the function types as a whole must satisfy the rules of
assignability.

So, yes, the type system is intentionally very literal in its
definition. Consider a slightly different example (using types A and B
above):

var z []A = ... // an expression yielding a value of the proper type
var x []B = z

According to expectations from the design of the language, the second
assignment should not cause an allocation of a new underlying array.
But for this assignment to work, such an allocation would be necessary.
So, the language designers decided that satisfying the expectations was
more important than doing something that hides a potentially large
allocation. (Just to be explicit, the above assignment will not
compile.)

In your case, however, a simple function literal will solve your
problem:

Initialize(func() B { return B(AConstructor()) })

In fact, the explicit conversion B(...) in the return statement is
unnecessary, but I think it makes it more clear that this is an
intentional conversion. It also makes it clear why the function literal
is necessary in the first place.

...Marvin

[1] https://golang.org/ref/spec#Assignability
[2] https://golang.org/ref/spec#Conversions
[3] https://golang.org/ref/spec#Function_types

Pascal de Kloe

unread,
Mar 16, 2021, 10:01:37 AM3/16/21
to golang-nuts

The structure encourages people to use function variables instead of method invocation (on an object or interface). Do the initialisation from main and the unit tests simply mock or stub the relevant calls per method.

func TestFoo(*testing.T) {
        bu := PublishFunc
        defer func() {
                PublishFunc = bu
        }()
        PublishFunc = mqtttest.New...


Op maandag 22 juli 2019 om 07:10:59 UTC+2 schreef wylde...@gmail.com:
Reply all
Reply to author
Forward
0 new messages