unit test for function which has goroutine

1,638 views
Skip to first unread message

Yvonne Zhang

unread,
Sep 10, 2020, 3:16:04 AM9/10/20
to golang-nuts
Hi,
I have a function streaming a zipreader to browser. It is like this.
func functionA(....)(body io.ReadCloser, err error){

   // some logic to get a zipreader

   body, pipeWriter := io.Pipe()
   zipWriter := zip.NewWriter(pipeWriter)

   go func(){
      // err := functionB(zipReader, zipWriter)call another function to prepare files and write to zipwriter
   If err != nil{
       zipWriter.Close()
        _ = pipeWriter.CloseWithError(err)
        return
   }
      zipWriter.Close()
       pipeWriter.Close()
   }()
   return 
}

My question is about unit test about this functionA. I have mock for functionB. How do I write unit test for functionA with the mock functionB. It seems my unit test does not work well when it goes into the goroutine.
Many thanks!

Gregor Best

unread,
Sep 10, 2020, 3:26:13 AM9/10/20
to golan...@googlegroups.com
If I were you, I'd pass in an interface value to functionA with the
signature

type Ber interface{
functionB(io.Reader, io.Writer)
}

(assuming that zipReader is an io.Reader, zipWriter is an io.Writer, you
may have to adjust this a bit).

Then make your mock functionB a method on a type that implements the Ber
interface, the same for the real functionB. In unit tests, pass in an
instance of the type with the mock method, in real life, pass in an
instance of the type with the real method.

Note that the types could be as simple as

type mockB struct{}
func (m mockB) functionB(...)
> --
> You received this message because you are subscribed to the Google
> Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to golang-nuts...@googlegroups.com
> <mailto:golang-nuts...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/4ca0817a-f1f7-44a9-be3d-3584bcb61b8an%40googlegroups.com
> <https://groups.google.com/d/msgid/golang-nuts/4ca0817a-f1f7-44a9-be3d-3584bcb61b8an%40googlegroups.com?utm_medium=email&utm_source=footer>.

--
Gregor Best
be...@pferdewetten.de

Yvonne Zhang

unread,
Sep 10, 2020, 3:35:29 AM9/10/20
to golang-nuts
Thank you Gregor for your quick reply!

Anything special I need to think about this go routine logic in my unit test. It might be still running after test returns..?

Gregor Best

unread,
Sep 10, 2020, 3:42:21 AM9/10/20
to Yvonne Zhang, golang-nuts
Right, I wanted to write something about the goroutine and forgot about
it. Sorry about that.

The problem here could be that in the code you posted there's nothing
waiting on any result from the goroutine, and if the test exits quickly
enough, it might not get scheduled.

In general, whenever a goroutine is created, you should have some idea
of when and how it gets to run and when it stops running, and how other
goroutines wait for it to do its job.

You could do this for example by controlling the lifetime of the
goroutine with a sync.WaitGroup. Pass in a WaitGroup and call Done() on
it when the goroutine returns (probably as a deferred call), then call
Wait() in some other goroutine (for example in your main function or in
the TestFoo function) so there's some sort of ordering enforced.

In general, you'll need some way of signalling "I'm done copying stuff
from zipReader to zipWriter" in the goroutine, and you'll need to
arrange for some other, longer lived, goroutine to wait for that signal.
> https://groups.google.com/d/msgid/golang-nuts/33648e9a-4011-4d02-a07b-01b506cf5cd1n%40googlegroups.com
> <https://groups.google.com/d/msgid/golang-nuts/33648e9a-4011-4d02-a07b-01b506cf5cd1n%40googlegroups.com?utm_medium=email&utm_source=footer>.

--
Gregor Best
be...@pferdewetten.de

Yvonne Zhang

unread,
Sep 11, 2020, 12:14:10 AM9/11/20
to golang-nuts
Thank you Gregor. As this is just to make unit test work, if I add waitgroup in the code, will this affect the real streaming logic?
Is that OK, I check if is it a mock function functionB called, then I tell it to sleep for a sec before return. 

roger peppe

unread,
Sep 11, 2020, 6:39:11 AM9/11/20
to Yvonne Zhang, golang-nuts
For the record, my usual response to this, assuming that function A and function B are not exported functions, is that you'd be better off not writing unit tests for the individual functions, but testing (as far as possible) against the publicly exported interface. I tend to think about what the overall contract is (the contract that really matters from the external consumer's point of view) and test that.

If you do that, then your tests will remain useful even if you decide to refactor the code. That is a significant part of the value of automated tests in my view.

In this particular case ISTM that the goroutine is just an implementation detail. If you set up the stream source somehow, you could use the net/http package to hit the endpoint that does the streaming and check that you're seeing the correct result. Depending on what the source is, you might need to fake it up somehow, but I'd always be inclined to use the lowest available level to do that, so you're testing as much of the stack as you can (and thus giving yourself most confidence in the code and most freedom to refactor later).

I'm aware that in saying this, I'm going directly against unit-testing advice available elsewhere. :) YMMV. And it's worth noting: when testing, there are no absolutes and context is crucial for making a decision as to what and how to test.

  hope this helps,
    rog.
  

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/4ca0817a-f1f7-44a9-be3d-3584bcb61b8an%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages