Mock functions in Go
Asked Answered
B

10

201

I'm puzzled with dependencies. I want to be able to replace some function calls with mock ones. Here's a snippet of my code:

func get_page(url string) string {
    get_dl_slot(url)
    defer free_dl_slot(url)
    
    resp, err := http.Get(url)
    if err != nil { return "" }
    defer resp.Body.Close()
    
    contents, err := ioutil.ReadAll(resp.Body)
    if err != nil { return "" }
    return string(contents)
}

func downloader() {
    dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore
    content := get_page(BASE_URL)
    links_regexp := regexp.MustCompile(LIST_LINK_REGEXP)
    matches := links_regexp.FindAllStringSubmatch(content, -1)
    for _, match := range matches{
        go serie_dl(match[1], match[2])
    }
}

I'd like to be able to test downloader() without actually getting a page through http - i.e. by mocking either get_page (easier since it returns just the page content as a string) or http.Get().

I found this thread which seems to be about a similar problem. Julian Phillips presents his library, Withmock as a solution, but I'm unable to get it to work. Here's the relevant parts of my testing code, which is largely cargo cult code to me, to be honest:

import (
    "testing"
    "net/http" // mock
    "code.google.com/p/gomock"
)
...
func TestDownloader (t *testing.T) {
    ctrl := gomock.NewController()
    defer ctrl.Finish()
    http.MOCK().SetController(ctrl)
    http.EXPECT().Get(BASE_URL)
    downloader()
    // The rest to be written
}

The test output is following:

ERROR: Failed to install '_et/http': exit status 1 output: can't load package: package _et/http: found packages http (chunked.go) and main (main_mock.go) in
/var/folders/z9/ql_yn5h550s6shtb9c5sggj40000gn/T/withmock570825607/path/src/_et/http

Is the Withmock a solution to my testing problem? What should I do to get it to work?

Boil answered 3/10, 2013 at 19:47 Comment(1)
Since you're diving into Go unit testing, look into GoConvey for a great way to do behavior-driven testing... and teaser: an automatically-updating web UI is coming that also works with native "go test" tests.Finalize
H
257

Personally, I don't use gomock (or any mocking framework for that matter; mocking in Go is very easy without it). I would either pass a dependency to the downloader() function as a parameter, or I would make downloader() a method on a type, and the type can hold the get_page dependency:

Method 1: Pass get_page() as a parameter of downloader()

type PageGetter func(url string) string

func downloader(pageGetterFunc PageGetter) {
    // ...
    content := pageGetterFunc(BASE_URL)
    // ...
}

Main:

func get_page(url string) string { /* ... */ }

func main() {
    downloader(get_page)
}

Test:

func mock_get_page(url string) string {
    // mock your 'get_page()' function here
}

func TestDownloader(t *testing.T) {
    downloader(mock_get_page)
}

Method2: Make download() a method of a type Downloader:

If you don't want to pass the dependency as a parameter, you could also make get_page() a member of a type, and make download() a method of that type, which can then use get_page:

type PageGetter func(url string) string

type Downloader struct {
    get_page PageGetter
}

func NewDownloader(pg PageGetter) *Downloader {
    return &Downloader{get_page: pg}
}

func (d *Downloader) download() {
    //...
    content := d.get_page(BASE_URL)
    //...
}

Main:

func get_page(url string) string { /* ... */ }

func main() {
    d := NewDownloader(get_page)
    d.download()
}

Test:

func mock_get_page(url string) string {
    // mock your 'get_page()' function here
}

func TestDownloader() {
    d := NewDownloader(mock_get_page)
    d.download()
}
Hessler answered 3/10, 2013 at 20:42 Comment(29)
Thanks a lot! I went with the second one. (there was some other functions too that I wanted to mock, so it was easier to assign them to a struct) Btw. I'm a bit love in Go. Especially its concurrency features are neat!Boil
@GolDDranks, happy to help. :) Yeah, Go is pretty fantastic. It develops fast and it runs fast and it's really fun to write. Especially when you spend all day fighting with C++. ;)Hessler
Doesn't NewDownloader need to have *Downloader as return type. Thanks by the way, this was really useful.Sororate
Am I the only one finding that for the sake of testing we have to change the main code / functions signature is terrible?Dilapidated
@webrc2 Yes, in TDD, you write the tests first and then the implementation. What I am a bit concerned is to change the code afterwards, for the sole purpose of tests.Dilapidated
@Hessler I agree that writing tests beforehands avoids problems :)Dilapidated
@Hessler I'm thinking PageGetter could/should be implemented like an interface with a method like Get. I'm comparing this with C# interfaces, for example. The intent of the PageGetter would be clearer this way.Eudora
@Mihai Maybe. I tried to stay consistent with the original example. Anyway, this intent is clearer to you because you're drawing on C# experience, and while C# has closures, it's Java heritage leads its practitioners to think in objects and interfaces (and to consider closures to be something akin to "convenient syntactic sugar"). Functional programmers find passing functions more natural than interfaces. Go is idiomatically multiparadigmatic: for example, net/http defines both Handler (interface) and HandlerFunc (function).Hessler
How do you achieve 100% code coverage? func get_page(url string) string { /* ... */ } is not testable and therefore full coverage is not achievedNoetic
@Noetic you're absolutely correct. The question was about testing downloader(), not get_page(). If you're interested in testing get_page(), you'll have to use an http.Client instead of http.Get() inside of get_page(). You'll also need to inject a real http.Client somehow, either by making get_page() a method on an object that has an http.Client pointer or by passing an http.Client into get_page() as a param. Here's an example (it won't compile) for how to test get_page: play.golang.org/p/3sV8unp5OZHessler
I am wondering why you create a NewDownloader function instead of just using d := Downloader{mock_get_page}? Forgive me for the question, I am pretty new to Go.Maryjanemaryjo
@Maryjanemaryjo Because Downloader.get_page is unexported, code from outside of this package wouldn't be able to construct a Downloader unless the package provided a member for setting this member. As far as why I didn't export it; I'm generally a little conservative about what I let client code do--for instance, it would create problems if someone did Downloader.GetPage = nil after the Downloader had been initialized (shame on them for doing that, but I still prefer to avoid the problem altogether).Hessler
You might want to define an interface instead, like: type PageGetter interface { getPage (url string) (content string, err error) } Then you can define types PageLoader and TestPageLoader with getPage() defined to satisfy the interface, and pass in instances to somewhere that download() can see.Demott
@MartinEllison For single-method things, I prefer to pass functions (less boilerplate). If you really need to pass multiple methods, interfaces are great. The net/http package supports both HandlerFunc and Handlers, for instance.Hessler
@Hessler what if you had, say, func (s *someStruct) get_page(url string) string and func (s *someStruct) downloader(), how would you mock them? Would you create an interface to cover every method, just so you could mock different versions of it for testing? It sounds silly, but there are many times when I see code with dozens of methods like that and I couldn't find an easy way around them without using an external lib like mockery.Seismism
@FilipeGorgesReuwsaat Yes, if you want to mock those things, you should use an interface. If you find yourself needing to mock lots of things, you should probably be testing against the real thing (this may mean your test is now an integration test). Tests with lots of mocks tend to be very low-value.Hessler
@Hessler "having automated tests is just an awesome secondary benefit" - I contend it's actually the third most important benefit. The first most important benefit is enabling fearless aggressive refactoring. The secondary benefit is the positive effect on the design of your implementation code. The third benefit is having a great test suite to verify stuff as you're writing it. :-)Unpractical
+1 for the comedy of "mocking in Go is very easy without it"... compared to something like Python, it's quite hard.Unpractical
@JonathanHartley While Python does make mocking very easily, I think Python's approach is strictly worse or at least harder to do properly. I've spent so much time tracking down issues where mocks were behaving as though methods existed when they didn't, causing failures far from the actual issue or worse: false positives. Beyond that, things like patch encourage unmaintainable code ("untestable" code in other languages). Similarly, because mocks are so easy, egregious mocking is common, which renders those tests close to useless. This is just my $0.02 as a professional Python dev.Hessler
@Hessler I agree that using mocks in inappropriate ways causes lots of problems. They definitely have their place, but their place is not everywhere. I don't have any problems with 'patch' (but then I was in the room when the first version of it was written) Indeed use of 'patch' is absolutely vital to undo the mocking you do. Any naive methods of doing this yourself, without using patch, will fail in confusing ways.Unpractical
@JonathanHartley I'm not sure what you mean by 'patch is absolutely vital to undo the mocking you do'. Generally I'm of the opinion that using patch is a sign that your code is structured poorly or you're testing things that you oughtn't; this philosophy has made my life quite a bit easier, but YMMV, of course.Hessler
@Hessler We're perhaps using 'patch' to talk about different things. I'm saying that unittest.mock.patch is good versus simply monkey patching things manually (eg. othermodue.Class.attribute = testfake) and then attempting to unpatch it manually afterwards. The undoing has a large number of surprising edge cases which patch takes care of for you, so if one is going to mock, then using patch is the way to go.Unpractical
If you are saying "don't use 'patch'" to mean "don't mock", then I absolutely agree that mocking is often over-used, and people should be wary of that. But to eliminate it entirely is, IMO, going too far. Unit tests need isolating, and you can't always dependency inject all the things. For example, if joining a project that hasn't been written that way, you don't have much choice.Unpractical
@JonathanHartley I wasn't using it that way; I meant 'patch' itself is hacky. If you need to mock something, 99% of the time it should be passed as a parameter (either a callable or an interface) such that it can be mocked or you shouldn't be mocking it at all (e.g., a database connection). If you have to use patch, your code isn't "testable" in the conventional sense, and 'being able to write tests' is not the primary value-add for testable code. This is how I think about it, anyway.Hessler
Alright, so it sounds like we mostly agree. DI FTW. But there are lots of cases where that isn't possible - most prominently, when working on a team of people who haven't been working that way. For situations like that, and a bunch of other edge cases, you really really need 'patch'.Unpractical
Doing this adds so much stupid bloat to my functions and structs when it's completely unnecessary. Go is a modern language, it should have this. Yeah writing with testing in mind is good but it shouldn't come at the cost of making it harder to write and read code.Addi
@Addi It's hard to tell from your comment what your specific complaint is (what does "it should have this" mean?). It seems like a general grievance with static type systems, which almost always require explicit (rather than implicit) polymorphism (and for good reasons, including readability contra your comment). If you find this painful, you're probably mocking too much, but it's hard to say for certain without more detail. Languages like Java or .Net probably auto generate mocks via class loading, but I don't know how that would work in an AOT language like Go (linker hacks?).Hessler
By this, I mean mocking objects should be easy and shouldn't require unnecessary method parameters or structs. Having to do this at all is painful. There are many valid use cases where mocking makes sense because you need to isolate your tests from other functionality. E.g you're using an external API and you need to test different cases where the API returns different kinds of output. Now every function call has to have an extra parameter where you pass the API interface or something. It shouldn't be necessary to modify the method signature just so you can test, it isn't 1982 anymore.Addi
Even if mocking were easy, you'd still need some way to pass those mocks in, which means "unnecessary parameters" or else patching which is an antipattern (in my 15 years of professional python development experience, it makes code brittle and tests hard to read or modify) and also it doesn't really apply to natively compiled languages like Go. Ultimately, it's a good thing that Go doesn't support patching IMO.Hessler
K
59

If you change your function definition to use a variable instead:

var get_page = func(url string) string {
    ...
}

You can override it in your tests:

func TestDownloader(t *testing.T) {
    get_page = func(url string) string {
        if url != "expected" {
            t.Fatal("good message")
        }
        return "something"
    }
    downloader()
}

Careful though, your other tests might fail if they test the functionality of the function you override!

The Go authors use this pattern in the Go standard library to insert test hooks into code to make things easier to test:

Kiyokokiyoshi answered 24/6, 2014 at 2:0 Comment(5)
Downvote if you want, this is an acceptable pattern for small packages to avoid boilerplate associated with DI. The variable containing the function is only "global" to the scope of the package since it's not exported. This is a valid option, I mentioned the downside, choose your own adventure.Kiyokokiyoshi
One thing to note is that function defined this way cannot be recursive.Crosshatch
I agree with @Kiyokokiyoshi that this approach has its place.Sorcery
"Careful though, your other tests might fail if they test the functionality of the function you override!" May be storing the original function before overriding and restoring it back after test will help. get_page_orig :=.get_page /* override get_page and test */ get_page =get_page_origAdoree
@BenSandler Late to the party, but you technically can make this recursive: var get_page func(); func init() { get_page = func() { get_page() } }Hessler
Z
15

I'm using a slightly different approach where public struct methods implement interfaces but their logic is limited to just wrapping private (unexported) functions which take those interfaces as parameters. This gives you the granularity you would need to mock virtually any dependency and yet have a clean API to use from outside your test suite.

To understand this it is imperative to understand that you have access to the unexported methods in your test case (i.e. from within your _test.go files) so you test those instead of testing the exported ones which have no logic inside beside wrapping.

To summarize: test the unexported functions instead of testing the exported ones!

Let's make an example. Say that we have a Slack API struct which has two methods:

  • the SendMessage method which sends an HTTP request to a Slack webhook
  • the SendDataSynchronously method which given a slice of strings iterates over them and calls SendMessage for every iteration

So in order to test SendDataSynchronously without making an HTTP request each time we would have to mock SendMessage, right?

package main

import (
    "fmt"
)

// URI interface
type URI interface {
    GetURL() string
}

// MessageSender interface
type MessageSender interface {
    SendMessage(message string) error
}

// This one is the "object" that our users will call to use this package functionalities
type API struct {
    baseURL  string
    endpoint string
}

// Here we make API implement implicitly the URI interface
func (api *API) GetURL() string {
    return api.baseURL + api.endpoint
}

// Here we make API implement implicitly the MessageSender interface
// Again we're just WRAPPING the sendMessage function here, nothing fancy 
func (api *API) SendMessage(message string) error {
    return sendMessage(api, message)
}

// We want to test this method but it calls SendMessage which makes a real HTTP request!
// Again we're just WRAPPING the sendDataSynchronously function here, nothing fancy
func (api *API) SendDataSynchronously(data []string) error {
    return sendDataSynchronously(api, data)
}

// this would make a real HTTP request
func sendMessage(uri URI, message string) error {
    fmt.Println("This function won't get called because we will mock it")
    return nil
}

// this is the function we want to test :)
func sendDataSynchronously(sender MessageSender, data []string) error {
    for _, text := range data {
        err := sender.SendMessage(text)

        if err != nil {
            return err
        }
    }

    return nil
}

// TEST CASE BELOW

// Here's our mock which just contains some variables that will be filled for running assertions on them later on
type mockedSender struct {
    err      error
    messages []string
}

// We make our mock implement the MessageSender interface so we can test sendDataSynchronously
func (sender *mockedSender) SendMessage(message string) error {
    // let's store all received messages for later assertions
    sender.messages = append(sender.messages, message)

    return sender.err // return error for later assertions
}

func TestSendsAllMessagesSynchronously() {
    mockedMessages := make([]string, 0)
    sender := mockedSender{nil, mockedMessages}

    messagesToSend := []string{"one", "two", "three"}
    err := sendDataSynchronously(&sender, messagesToSend)

    if err == nil {
        fmt.Println("All good here we expect the error to be nil:", err)
    }

    expectedMessages := fmt.Sprintf("%v", messagesToSend)
    actualMessages := fmt.Sprintf("%v", sender.messages)

    if expectedMessages == actualMessages {
        fmt.Println("Actual messages are as expected:", actualMessages)
    }
}

func main() {
    TestSendsAllMessagesSynchronously()
}

What I like about this approach is that by looking at the unexported methods you can clearly see what the dependencies are. At the same time the API that you export is a lot cleaner and with less parameters to pass along since the true dependency here is just the parent receiver which is implementing all those interfaces itself. Yet every function is potentially depending only on one part of it (one, maybe two interfaces) which makes refactors a lot easier. It's nice to see how your code is really coupled just by looking at the functions signatures, I think it makes a powerful tool against smelling code.

To make things easy I put everything into one file to allow you to run the code in the playground here but I suggest you also check out the full example on GitHub, here is the slack.go file and here the slack_test.go.

And here the whole thing.

Zamir answered 11/1, 2018 at 11:53 Comment(4)
This is actually an interesting approach and the tidbit about having access to private methods in the test file is really useful. It reminds me of the pimpl technique in C++. However, I think it should be said that testing private functions is dangerous. Private members are usually considered implementation details and are more likely to change over time than the public interface. As long as you only test the private wrappers around the public interface, though, you should be fine.Smocking
Yeah generally speaking I'd agree with you. In this case though the private methods bodies are exactly the same as the public ones so you'll be testing exactly the same thing. The only difference between the two are the function arguments. That's the trick that allows you to inject any dependency (mocked or not) as needed.Zamir
Yeah, I agree. I was just saying as long as you limit it to private methods that wrap those public ones, you should be good to go. Just don't start testing the private methods that are implementation details.Smocking
It might be worth noting this approach heavily impacts code coverage tooling. The coverage % will drop quite a bit with tools like go test -cover because of the untested public APIs.Dorm
B
10

I would do something like,

Main

var getPage = get_page
func get_page (...

func downloader() {
    dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore
    content := getPage(BASE_URL)
    links_regexp := regexp.MustCompile(LIST_LINK_REGEXP)
    matches := links_regexp.FindAllStringSubmatch(content, -1)
    for _, match := range matches{
        go serie_dl(match[1], match[2])
    }
}

Test

func TestDownloader (t *testing.T) {
    origGetPage := getPage
    getPage = mock_get_page
    defer func() {getPage = origGatePage}()
    // The rest to be written
}

// define mock_get_page and rest of the codes
func mock_get_page (....

And I would avoid _ in golang. Better use camelCase

Bouillon answered 11/5, 2015 at 6:55 Comment(5)
would it be possible in go to develop a package that could do this for you. I'm thinking something like: p := patch(mockGetPage, getPage); defer p.done(). I am new to go, and was trying to do this using the unsafe library, but seems impossible to do in the general case.Kv
@Bouillon this is almost exactly my answer written over a year after mine was.Kiyokokiyoshi
1. The only similarity is the global var way. @Kiyokokiyoshi 2. Simple is better than complex. weberc2Bouillon
@fallen I don't consider your example to be simpler. Passing arguments is not more complex than mutating global state, but relying on global state introduces a lot of problems that don't exist otherwise. For example, you will have to deal with race conditions if you want to parallelize your tests.Hessler
It's almost the same, but it's not :). In this answer, I see how to assign a function to a var and how this allows me to assign a different implementation for tests. I cannot change the arguments on the function I'm testing, so this is a nice solution for me. The alternative is to use Receiver with mock struct, I don't know yet which one is simpler.Redintegration
M
4

the simplest way is to set function into a global variable and before test set your custom method

// package base36

func GenerateRandomString(length int) string {
    // your real code
}


// package teamManager

var RandomStringGenerator = base36.GenerateRandomString

func (m *TeamManagerService) CreateTeam(ctx context.Context) {
 
    // we are using the global variable
    code = RandomStringGenerator(5)
 
    // your application logic

    return  nil
}

and in your test, you must first mock that global variable

    teamManager.RandomStringGenerator = func(length int) string {
        return "some string"
    }
    
   service := &teamManager.TeamManagerService{}
   service.CreateTeam(context.Background())
   // now when we call any method that user teamManager.RandomStringGenerator, it will call our mocked method

another way is to pass RandomStringGenerator as a dependency and store it inside TeamManagerService and use it like this:

// package teamManager

type TeamManagerService struct {
   RandomStringGenerator func(length int) string
}

// in this way you don't need to change your main/where this code is used
func NewTeamManagerService() *TeamManagerService {
    return &TeamManagerService{RandomStringGenerator: base36.GenerateRandomString}
}

func (m *TeamManagerService) CreateTeam(ctx context.Context) {
 
    // we are using the struct field variable
    code = m.RandomStringGenerator(5)
 
    // your application logic

    return  nil
}

and in your test, you can use your own custom function

    myGenerator = func(length int) string {
        return "some string"
    }
    
   service := &teamManager.TeamManagerService{RandomStringGenerator: myGenerator}
   service.CreateTeam(context.Background())

you are using testify like me :D you can do this

// this is the mock version of the base36 file
package base36_mock

import "github.com/stretchr/testify/mock"

var Mock = mock.Mock{}

func GenerateRandomString(length int) string {
    args := Mock.Called(length)
    return args.String(0)
}

and in your test, you can use your own custom function

   base36_mock.Mock.On("GenerateRandomString", 5).Return("my expmle code for this test").Once()
    
   service := &teamManager.TeamManagerService{RandomStringGenerator: base36_mock.GenerateRandomString}
   service.CreateTeam(context.Background())
Mama answered 6/12, 2021 at 12:56 Comment(0)
H
0

Warning: This might inflate executable file size a little bit and cost a little runtime performance. IMO, this would be better if golang has such feature like macro or function decorator.

If you want to mock functions without changing its API, the easiest way is to change the implementation a little bit:

func getPage(url string) string {
  if GetPageMock != nil {
    return GetPageMock()
  }

  // getPage real implementation goes here!
}

func downloader() {
  if GetPageMock != nil {
    return GetPageMock()
  }

  // getPage real implementation goes here!
}

var GetPageMock func(url string) string = nil
var DownloaderMock func() = nil

This way we can actually mock one function out of the others. For more convenient we can provide such mocking boilerplate:

// download.go
func getPage(url string) string {
  if m.GetPageMock != nil {
    return m.GetPageMock()
  }

  // getPage real implementation goes here!
}

func downloader() {
  if m.GetPageMock != nil {
    return m.GetPageMock()
  }

  // getPage real implementation goes here!
}

type MockHandler struct {
  GetPage func(url string) string
  Downloader func()
}

var m *MockHandler = new(MockHandler)

func Mock(handler *MockHandler) {
  m = handler
}

In test file:

// download_test.go
func GetPageMock(url string) string {
  // ...
}

func TestDownloader(t *testing.T) {
  Mock(&MockHandler{
    GetPage: GetPageMock,
  })

  // Test implementation goes here!

  Mock(new(MockHandler)) // Reset mocked functions
}
Haileyhailfellowwellmet answered 1/10, 2019 at 17:55 Comment(0)
T
0

I have been in similar spot. I was trying to write unitTest for a function which had numerous clients calling it. let me propose 2 options that I explored. one of which is already discussed in this thread, I will regardless repeat it for the sake of people searching.

Method 1: Declaring function you wanna mock as a Global variable


one option is declaring a global variable (has some pit falls).

eg:

package abc

var getFunction func(s string) (string, error) := http.Get

func get_page(url string) string {
  ....
  resp, err := getFunction(url)
  ....
}

func downloader() {
  .....
}

and the test func will be as follows:

package abc

func testFunction(t *testing.T) {
  actualFunction := getFunction
  getFunction := func(s string) (string, error) { 
     //mock implementation 
  }
  defer getFunction = actualFunction
  .....
  //your test
  ......
}

NOTE: test and actual implementation are in the same package.

there are some restrictions with above method thought!

  1. running parallel tests is not possible due to risk of race conditions.
  2. by making function a variable, we are inducing a small risk of reference getting modified by future developers working in same package.

Method 2: Creating a wrapped function


another method is to pass along the methods you want to mock as arguments to the function to enable testability. In my case, I already had numerous clients calling this method and thus, I wanted to avoid violating the existing contracts. so, I ended up creating a wrapped function.

eg:

package abc

type getOperation func(s string) (string, error)

func get_page(url string, op getOperation) string {
  ....
  resp, err := op(url)
  ....
}

//contains only 2 lines of code
func downloader(get httpGet) {
  op := http.Get
  content := wrappedDownloader(get, op)
}

//wraps all the logic that was initially in downloader()
func wrappedDownloader(get httpGet, op getOperation) {
  ....
  content := get_page(BASE_URL, op)
  ....
}

now for testing the actual logic, you will test calls to wrappedDownloader instead of Downloader and you would pass it a mocked getOperation. this is allow you to test all the business logic while not violating your API contract with current clients of the method.

Toshikotoss answered 28/4, 2022 at 2:56 Comment(0)
V
0

I am new to Golang, but I have spent several days trying to find a way to mock the functions of third-party packages such as http.Get, without modifying the source code. At this time, I am forced to conclude that it is not possible in Golang, which is a huge disappointment.

Varmint answered 14/11, 2023 at 16:1 Comment(0)
P
0

It's 2024, we have xgo now: https://github.com/xhd2015/xgo. The xgo have unlocked function interceptor for go. Example:

package main

import (
    "context"
    "fmt"

    "github.com/xhd2015/xgo/runtime/core"
    "github.com/xhd2015/xgo/runtime/mock"
)

func main() {
    before := add(5, 2)
    fmt.Printf("before mock: add(5,2)=%d\n", before)
    mock.AddFuncInterceptor(add, func(ctx context.Context, fn *core.FuncInfo, args core.Object, results core.Object) error {
        a := args.GetField("a").Value().(int)
        b := args.GetField("b").Value().(int)
        res := a - b
        results.GetFieldIndex(0).Set(res)
        return nil
    })
    after := add(5, 2)
    fmt.Printf("after mock: add(5,2)=%d\n", after)
}

func add(a int, b int) int {
    return a + b
}

Disclaimer: creator of the xgo project after years of diving into go compiler.

Poynter answered 24/3 at 11:29 Comment(0)
L
-1

Considering unit test is the domain of this question, highly recommend you to use monkey. This Package make you to mock test without changing your original source code. Compare to other answer, it's more "non-intrusive".

main

type AA struct {
 //...
}
func (a *AA) OriginalFunc() {
//...
}

mock test

var a *AA

func NewFunc(a *AA) {
 //...
}

monkey.PatchMethod(reflect.TypeOf(a), "OriginalFunc", NewFunc)

Bad side is:

  • Reminded by Dave.C, This method is unsafe. So don't use it outside of unit test.
  • Is non-idiomatic Go.

Good side is:

  • Is non-intrusive. Make you do things without changing the main code. Like Thomas said.
  • Make you change behavior of package (maybe provided by third party) with least code.
Lauricelaurie answered 8/8, 2019 at 3:22 Comment(5)
Please don't do this. It's completely unsafe and can break various Go internals. Not to mention it's not even remotely idiomatic Go.Thruway
@DaveC I respect your experience about Golang, but suspect your opinion. 1. Safety does not mean all for software development, do feature-rich and convenience matter. 2. Idiomatic Golang is not Golang, is part of it. If one project is open-source, it's common for other people to play dirty on it. Community should encourage it at least not suppress it.Lauricelaurie
The language is called Go. By unsafe I mean it can break the Go runtime, things like garbage collection.Thruway
To me, unsafe is cool for a unit test. If refactoring code with more 'interface' is needed every time a unit test is made. It fits me more that use an unsafe way to solve it.Lauricelaurie
@DaveC I fully agree this is a terribly idea (my answer being the top-voted and accepted answer), but to be pedantic I don't think this will break GC because the Go GC is conservative and meant to handle cases like this. I would be happy to be corrected, however.Hessler

© 2022 - 2024 — McMap. All rights reserved.