Edwin O.
Edwin O.

Reputation: 5276

testing a go closure

i have the following function that returns a closure in golang, any idea/reference how can possibly write a test for it?

  type (
    OrderRepoInterface interface {
       func save(msg Message) error
    }

    // OrderAggregation represents an event handler
    EventHandler struct {
        repo         OrderRepoInterface // in main.go i pass a concrete repository here
    }

    VersionedEventHandler struct {
       function func(msg *Message) error
    }

    Message struct {
       version int
       payload string
    }

 )

    func (o *EventHandler) OnOrderWasCreated() VersionedEventHandler {
        return func(msg *Message) error {
            msg.version = 1
            err := o.repo.save(msg)
            return err
        }
    }

ps

this is not the real code, as i am using a couple of libraries, i drafted this question with the above code, hoping that it would give an idea what i am trying to achieve, so it may not compile

edit

what i am after is to see some idiomatic examples or ideas where a function that returns an anonymous function is tested in go.

so i don't necessary need a working solution.

Upvotes: 0

Views: 1595

Answers (1)

William
William

Reputation: 410

It would help to have a working example, so I provided one. For this simple case, it would probbably suffice to just have a dict which maps version numbers to functions which handle the saving of an order. But I have tried to implement closer to what you have provided, with an order handler interface.

For testing, you will want some sort of log to capture side effects, so that you can verify that the correct handler is being called. To do that, I added a global string array called eventLog which can be appended to. For testing, you will want to create more MultiVersionHandlers, and more test Messages.

You will want to verify that the handlers respond to the test messages in the way you imagine, by calling the save() method on the handlers and comparing the contents of the eventLog with what you expected. Also, you will want to create messages that should fail. These messages would not map to a version that the handler supports. You then verify that the proper error value is returned. I have done some of this for you.

package main

import "fmt"

type MultiVersionHandler struct {
    handlers map[int]OrderRepoInterface
}

type Message struct {
    version int
    payload string
}

type OrderRepoInterface interface {
    save(Message) error
}

type OrderHandler struct {
    saveHandler func(Message) error
}

// let's implement the OrderRepoInterface for a regular order handler
func (oh OrderHandler) save(msg Message) error {
    return oh.saveHandler(msg)
}

// let's implement the OrderRepoInterface for a multi version order handler
func (mh MultiVersionHandler) save(msg Message) error {

    if handler, ok := mh.handlers[msg.version]; ok {
        return handler.save(msg)
    }

    return fmt.Errorf("doesn't support version %d, payload %q",
            msg.version,
            msg.payload)

}

// We will use eventLog capture simulations of a log of events which
// happen via our handlers. Useful for verification.
var eventLog = []string{}

func main() {

    multiHandler := MakeMultiHandler()

    msg1 := Message{payload: "make me a burger", version: 1}

    msg2 := Message{payload: "make me a cake", version: 2}

    msg3 := Message{payload: "make me a robot", version: 3}

    // Create a message which has no handler.
    // This message for version 4, should cause an error.
    msg4 := Message{payload: "make me a planet", version: 4}

    err := multiHandler.save(msg1)
    err  = multiHandler.save(msg2)
    err  = multiHandler.save(msg3)
    err  = multiHandler.save(msg4)
    if err != nil {
        fmt.Printf("Expecting an error: %q\n", err.Error())
        // Expecting an error: 
        //  "does not have a handler for version 4.
        //   Cannot process payload "make me a planet""

    }

    fmt.Printf("Event Log:%#v", eventLog)
    // Event Log:[]string{
    //   "Amy will make me a burger", 
    //   "Brandy will make me a cake", 
    //   "Caleb will make me a robot"}        



}

// This makes a multi version handler for an example. 
// You should create more of these for testing different scenarios.
func MakeMultiHandler() OrderRepoInterface {

    amy := OrderHandler{
        saveHandler: func(msg Message) error {
            action := "Amy will " + msg.payload
            eventLog = append(eventLog, action)
            return nil
        },
    }

    brandy := OrderHandler{
        saveHandler: func(msg Message) error {
            action := "Brandy will " + msg.payload
            eventLog = append(eventLog, action)
            return nil
        },
    }

    caleb := OrderHandler{
        saveHandler: func(msg Message) error {
            action := "Caleb will " + msg.payload
            eventLog = append(eventLog, action)
            return nil
        },
    }

    multiHandler := MultiVersionHandler{
        handlers: map[int]OrderRepoInterface{
            1: amy,    // amy should handle version 1 message
            2: brandy, // brandy should handle version 2 message
            3: caleb,  // caleb should handle version 3 message
        },
    }

    return multiHandler

}

Upvotes: 3

Related Questions