Please purchase the course to watch this video.

Full Course
GoMock, a powerful mocking framework for Go now maintained by Uber, streamlines unit testing by allowing developers to simulate dependencies like databases or user interactions, making tests more reliable and automated. By leveraging Go's interfaces and applying dependency injection—preferably using the option pattern for flexibility—developers can decouple components, enabling easier mocking of specific behaviors within their applications. While in-memory databases suit integration tests, mocks are ideal for simulating edge cases and interactions that are difficult or undesirable to reproduce in tests, such as text editor invocations. The lesson demonstrates how to handcraft mocks, but emphasizes the efficiency and configurability gained by using GoMock's MockGen tool to auto-generate mocks, set method expectations, and verify test outcomes. Managing generated mock code is simplified with Go Generate directives and .gitignore practices, ensuring workflow cleanliness. Overall, combining interfaces, dependency injection patterns, and automated mocking with GoMock leads to clearer, maintainable, and more thorough test coverage in Go projects.
No links available for this lesson.
Another one of my favorite packages when it comes to working with testing in Go is the GoMock package, provided by Uber. Although originally this was provided by the Go team, however it's since been taken over by the team at Uber, who now maintain it.
This package, gomock
is a mocking framework for the Go programming language, and allows you to easily create mock types when it comes to your actual testing.
What is a Mock and When to Use It
But what is a mock type, and when would you use it? Well, a mock is used for testing in order to simulate a real world dependency. So, for example, rather than connecting to, say, a real database, you would instead generate a mock one that allows you to be able to control the data that comes back or the behaviour of the database mock, as well as checking to see if the correct data was added to it.
Mocks are really useful when it comes to unit testing, but personally, I prefer to use real database instances when it comes to more integration tests. Knowing when to use a mock and when to use a real instance is a bit of an art. And in the next module, when I go through and actually build a full application from scratch, you'll hopefully get a bit of understanding from my own thought process about when to use each one.
However, in this lesson, we're just going to go ahead and take a look at the GoMock package and how we can use it, specifically when it comes to mocking out some code inside of my application.
Understanding the Application Behavior
But in order to understand how that works, let's first take a look at the application behaviour. Currently, I have my create command, which is reasonably concise. Here, I'm checking to see if the title flag was set, pulling out the title from it, loading the database, creating a new handler for this, the new posts handler, which we'll take a look at more in detail in a minute, creating the posts, which will create it into the database and do some other tasks, and then printing out that the post was created.
If I go ahead and run this using the go run
command with the posts add
command, you can also use create, but in this case, add is available as well. By the way, the code for this is in the link in the description down below. If I go ahead and pass in the title of "my new blog post", let's say, and we should run this, it opens up my text editor with a markdown file, "my new blog post", and I can go ahead and actually add in the content that I want.
So in this lesson, let's say we're going to learn about mocking, and then I could do other markdown format here, learning about dependency injection, which is useful when it comes to mocking interfaces, which is how mocking works, and then the actual mock gen or mockgen, which is a CLI tool that we're going to use in this lesson.
In any case, if I go ahead and save this, you can see "my post was created". And if I go ahead and run the posts list
command, post list
, sorry, you can see here it is created and added into my actual database.
The Posts Handler Code
To show where the code for this actually lives, it's inside of the handler/posts
package, or the package handler inside of the posts.go
file. Here I have a type called Posts
, which is my posts handler, which is constructed using the NewPosts
function, which accepts a sql.DB
type inside of it. This is called dependency injection, and is actually very important when it comes to testing in Go. We'll take a look at how this works in a minute when it comes to my own test code, and how we can also make use of dependency injection when it comes to mocking.
As for the actual create function itself, in this case, I create a new repository from this database that's been injected in. Then I go ahead and use the sluggify package from the last lesson to create a slug from the title. Then I set this as the initial content with the kind of title tag for markdown beforehand. Then I go about creating a text editor using the editor package with the text struct, give it the extension of markdown, and then I go about using the output string in order to get the string from this markdown, opening up the text editor, and then insert all of the data into the database.
Current Testing Approach
To go along with this, I have a test already underneath the post_test.go
file, which is the TestCreatePost
method. Again, this does the same thing sets up a database. However, this time it calls the LoadInMemory
function, which if I go ahead and open up a new tab for, and we can go to database, you can see I have, where is it. LoadInMemory
, the load_in_memory.go
file, which just returns Load
with file, but passes in the file as the following :memory:
name. This tells SQLite to load an in-memory database, which is really good when it comes to testing, as you start with a clean slate every time your test begins.
You can see here, my Load
function just specifies the local path to a db.sqlite
, and they both do the exact same thing, opening up the SQLite file, performing migrations, et cetera, et cetera. This is where that dependency injection comes in, because I create a new in-memory database, and then I pass it into the actual posts handler. Therefore, I'm able to constrain which database the posts handler is operating on, which means for testing, I can go ahead and actually use it, knowing that it will be fresh every time.
Additionally, at the end of this test, you can see that we're checking the database to see whether or not it has the post content inside, using the testify package we looked at in the last lesson. So in any case, we create the in-memory database, then we specify our inputs, which in this case is a context.Background()
and "my test blog post". Then we set up our expectations, which is a wantSlug
. So this is the slug we expect to be returned, as well as an error. We create the handler passing in our database. Then we go ahead and call the create function or create method of that handler. Then we go ahead and assert the results. And then we go ahead and check the database. Pretty simple stuff.
The Testing Problem
However, whilst this is all set up nicely, if I go ahead and test this code using the go test
command, you can see it just hangs indefinitely. This is because the code itself is trying to open up a text editor, which takes control of both standard output, standard error and standard input. And inside of the test context, this causes it to hang indefinitely. As you can see, I have to go ahead and cancel.
Now, this may seem like it's a problem with my code, but actually it's a very common problem when it comes to test code, in that being able to test user interaction is generally not advised. This is because you want the tests to run in an automated way, and therefore you want to simulate the user interaction instead.
This is where mocking is a great candidate for doing so, because you can just mock the user interaction itself. You can, of course, also mock things like the database. However, I find that working with an in-memory database is actually going to be a better experience. There are situations where you will want to use a mock database, such as being able to handle things such as connection timeouts or, you know, permission errors. By using a mock database, you're going to be able to handle failure better. But in our case, we're not testing for failure. We're just testing more for success. Although you should test for failure in the real world.
In any case, in our situation, we want to go ahead and actually mock out the text editor, which is defined inside of the editor package with the text struct, which has a couple of methods inside, the OutputString
, which is the one that we're using, and the OpenString
as well. So let's go ahead and make use of mockgen in order to do that.
Preparing for Dependency Injection
However, before we use mockgen, we actually have to do a couple of things beforehand. If I head on over to the posts handler, here you can see I'm specifying the text editor inside of the create command, where I provide it the option of the extension. Then I go ahead and call this inside of the create function. In order to be able to use this, we're going to need to first define the text editor as a dependency of the posts struct.
So to begin, let's go ahead and just define textEditor
here, which is going to be the editor.Text
property:
type Posts struct {
db *sql.DB
textEditor *editor.Text
}
Then rather than creating this inside of the actual create function, there's actually no need to do this. We can instead create it inside of the NewPosts
constructor, where we just set textEditor
to be the of type editor.Text
. And I'm going to go ahead and specify this as an ampersand, just so that we take this as a pointer, which we're going to need to do in a minute anyway:
func NewPosts(db *sql.DB) *Posts {
return &Posts{
db: db,
textEditor: &editor.Text{},
}
}
Now I can just go ahead and change my code to reference the text editor inside of the actual handler itself. This so far will have the exact same behavior as what we've got already. But now that we have it defined inside of our construction, we can start doing some interesting things with it.
Creating an Interface
In order to be able to mock this so that we can generate the output, the first thing we're going to need to do is to define an interface. We've already looked at interfaces a few times when it comes to Go, specifically things such as the io.Reader
and the io.Writer
. Interfaces are used to define a type that has a set of methods. And any type that conforms to those or implements those set of methods conforms to that interface.
So let's go ahead and define an interface as follows. We'll define this as kind of the TextEditor
interface:
type TextEditor interface {
OutputString(context.Context, string, []byte) (string, error)
}
So this is a type that conforms to a text editor. And we can do this by using the following line of type TextEditor interface
. Next, inside of this, we want to define the set of methods that a type should conform to in order for it to be considered a text editor. In this case, because we're going to replace our editor.Text
type with the TextEditor
interface, then we need to make sure it has the same method that we're using inside of our create method or create function.
This is the OutputString
method, which accepts a context.Context
, a string, and a slice of bytes, and returns a string and an error. So let's go ahead and define that. We can define it as OutputString
, and we want to accept a context.Context
, a string, and a slice of byte. And then we're going to go ahead and return a string and an error. That's all we're defining inside of our TextEditor
interface.
So now we can go ahead and replace the editor.Text
type inside of our posts struct with this TextEditor
interface, and everything should work as it did before:
type Posts struct {
db *sql.DB
textEditor TextEditor
}
If I go ahead and run this code again, using the go run
command, we'll do the posts add
, and go "my second post", let's say. This should all work as it did before. As you can see, it does. I'm just going to go ahead and exit this. This is because the editor.Text
type already conforms to the TextEditor
interface.
And one of the really nice things about Go, especially compared to other languages, is that you don't need to specify, or you don't need to implicitly specify, that a type conforms to an interface. For example, in Java or Rust, you would do, say, text
or editor.Text.implements.TextEditor
, or something like that, right. And then you would go ahead and actually add in the implementation into this block. In Go, you don't need to do that. Go has what's known as implicit conformance when it comes to interfaces. So if the interface is available to the package, and a type has all of the methods that are defined inside of that interface, then it automatically conforms, which makes it a really great tool for decoupling.
The Options Pattern for Dependency Injection
So now that we have our interface defined, let's actually go ahead and add a new dependency injection into our NewPosts
function. So rather than the NewPosts
itself defining the text editor that it wants, let's go ahead and inject this in, which we can do as follows, defining a property of textEditor
, and we can then go ahead and use the textEditor
field as follows.
Whilst this is one way to do this, it's not my preferred approach when it comes to dependency injection. This is the most simplest approach, and then just wherever we create this NewPosts
function, we could input this. However, for me, my issue with this approach is that it kicks the can of initialization to somewhere else. Instead, it would be nice if we could initialize the default, and then we could overwrite this default when we needed to.
To do that in Go, you make use of something called an option, or the option pattern. So in this case, you would define a new option called type Option
, which is going to be a function that accepts a *Posts
as a parameter, such as follows:
type Option func(*Posts)
By defining this option, we can then go ahead and create a new option function. So we could then go ahead and create a new function called WithTextEditor
, let's say, and this will return an Option
. We also need to define the text editor that we want to go ahead and pass in, so editor
, and then we can do TextEditor
. And this then returns an Option
, which is a function:
func WithTextEditor(editor TextEditor) Option {
return func(p *Posts) {
p.textEditor = editor
}
}
So let's go ahead and do this. By the way, I'm going to leave some resources in here because this may be going over people's heads. I'm kind of going through this to make sure that the video doesn't take up too much time, but this is my preferred option.
So what we're defining is we have an option, which is a function of *Posts
, and we're defining this as a text editor. Then we're going to go ahead and return an option, which is a kind of *Posts
, which is a function that has a property of *Posts
as follows. Then inside of this option, we can use it to overwrite the internal text editor. So we're doing p.textEditor = editor
.
Okay, so now that we have our option defined and the option WithTextEditor
defined as well, which is an option that we created, how do we go about using this when it comes to our actual code. Well, here what we can do is accept a variadic parameter called opts
, which is of type Option
:
func NewPosts(db *sql.DB, opts ...Option) *Posts {
res := &Posts{
db: db,
textEditor: &editor.Text{},
}
for _, opt := range opts {
opt(res)
}
return res
}
And then we can go ahead and make use of this. So let's go ahead and actually do res
here. And then we can go ahead and iterate through each of these options. So for _, opt := range opts
. And then we could just go ahead and call each option, passing in our results, and then make sure to return it.
By doing this, we basically allowed for optional dependency injections, which for me is a much more preferred approach to say requiring them as a hard requirement, which in that case, you would cause any kind of dependencies already to need to specify a dependency in any case. By doing it this way, we're allowing us to still be able to test things using black box tests because we don't have access to the internal properties, but we're able to specify this conditionally whenever we need to.
This may go over your head, but you'll see why this is useful in a minute. It just means that inside of our code that we're already specifying, we don't have to make any changes. So if we go to the create method here, you can see I don't need to specify anything here. Whereas if we changed that property, we would need to create a new text editor, which starts to get into the implementation details.
When it comes to like languages such as Java, they're okay with those kind of dependency injections. In my case, I much prefer to use the option pattern just because I think it makes things more simple. I'm probably waffling on a little bit just to try to make sure that I'm explaining this properly, so I do apologize.
In any case, with now our text editor interface defined, and with the ability to overwrite the internal text editor property of our posts handler without requiring us to do so, we're now in a good spot to be able to use the GoMock package in order to create a mock of this text editor interface.
Manual Mocking First
Before we take a look at MockGen, however, let's first take a look at how we can mock our code by hand, because I think it's useful to understand what mocking is and then what benefits MockGen provides.
So if we head on over to the posts test, let's go ahead and implement our own initial mock, which we can do by using the type, and then we can do kind of mockEditor
of a struct as follows:
type mockEditor struct {
result string
err error
}
func (e *mockEditor) OutputString(ctx context.Context, title string, data []byte) (string, error) {
return e.result, e.err
}
Okay, with our mock editor defined, let's go ahead and now try to pass this in to our NewPosts
function, which we can do by using the handler.WithTextEditor
option that we already have. As you can see, this accepts a handler.TextEditor
type. So let's go ahead and create an instance of our mock:
editor := &mockEditor{
result: "my test blog post",
err: nil,
}
So we can go ahead and say mockEditor
create, and we can just do this as follows. So we can do editor
, and we'll assign it to be a value of the mockEditor
as follows. As you'll see here that we're defining this as a pointer, we need to do this when it comes to interfaces.
Now we can just go ahead and pass this in as follows. However, as you can see, I'm getting an error. And if I try to go ahead and test this code, you can see the error is "cannot use editor as handler because it does not implement handler.TextEditor. Missing method OutputString".
Well, that's fine. Let's go ahead and actually implement this method. So we can do so as follows. Defining the receiver of e
, which is going to be the mockEditor
or a pointer to mockEditor
. And then we can do OutputString
, which accepted a context.Context
, a string of the kind of title. And then it also accepted the data, which is going to be a slice of bytes. I'm going to make sure to kind of do this across multiple lines because I think my face might be covering this. And then this had a return value of a string and an error.
For the moment, let's just go ahead and return "my content", let's say. And then we'll return a nil. Now you can see the error has disappeared and we have our mockEditor
in place making use of it.
Now, if I go ahead and actually run this code, you can see this time it works or it doesn't hang at least. We are getting a failure. The failure is, however, because we're expecting or our expectations is actually slightly different. Let's go ahead and swap this round. I've got my expectations and assertions the wrong way. And okay, let's go ahead and now run this.
The failure that we're getting is because we're expecting this. So "my test blog post", slug, this is actually fine. You can see here a little bit better with thanks to testify. So we're expecting "my test blog post", but we're actually getting back "my content". Now we could just go ahead and actually change this inside of the content. So we could do "my content" as follows. And now we can go ahead and run this and it's now passing. which is great.
However, when it comes to mocking, it's also good to be able to change the different parameters that you can actually return. In our case, we're just hard coding this, which is fine. But let's say we had table driven tests and we wanted to test different return values. In that case, we may want to be able to configure this even further:
type mockEditor struct {
result string
err error
}
func (e *mockEditor) OutputString(ctx context.Context, title string, data []byte) (string, error) {
return e.result, e.err
}
So to do so, we could go ahead and define like a result
inside of this struct, which would be a string and then any errors
that we may want as well. I've done this the wrong way around. And then inside of here, we can just go ahead and actually return e.result
and e.err
.
And now we're able to better configure this mock editor. So here we could go ahead and say the result is, and we could do "my first" or "my test blog post", make it a little bit more convincing. And then the error in this case would be nil. Or if we wanted to simulate or see how our code responds to an error, we could instead define an error here and then check to see if the error was existing. We'll take a look at that very shortly.
But now if I go ahead and change this to be "my test blog post", we should see that everything is passing, which it is. And if we go ahead and actually define, say, an error, so we could do like the wantError
here is going to be errors.New
, "something broke", and then we could return this here. We can now go ahead and run this and we should, our test should fail because we're returning an error, which it is.
So we can go ahead and change our expectation. We can set this to be empty. And in this case, we actually don't want there to be any errors. So we would assert that there is no, we could do kind of assert.Len
and the posts should be zero. We need to pass in the T. Let's go ahead and get rid of this for the moment. Now we can go ahead and test this again and everything is passing.
This time we've added in a simple test to be able to handle any error state. So that explains why mocking is useful and it allows you to be able to handle user input, say, without causing your test to break. But also you can do other things with it, like specifying different types of errors or different types of return values that may take place. It's very useful for being able to constrain the different types or test different levels of coverage.
However, whilst setting up mocks this way is perfectly valid, there's maybe other features that we might want to have. For instance, we want to probably test that we're getting the right values that come back into our code. And in order to do this, we would probably need to accept the testing.T
type. And then we would also have to kind of specify expectations inside of this editor. So we're expecting the title or expectation title is a string. Expectation data is a slice of bytes. And we would have to go about actually implementing the checks inside of the output string.
It starts to get a little bit tedious. Additionally, we may also want to have different behavior depending on what these parameters were called with. So for one title, we may want to return a different result than another title as well. Just to simulate all of the different workflows that could take place when it comes to mocking.
Building this out by hand would be kind of tedious. So fortunately, that's where mockgen comes in, which provides a number of different tools and utilities for being able to create very dynamic mocks.
Installing and Using MockGen
It achieves this through the use of code generation, which is done using the provided mockgen CLI tool that the package provides. So let's go ahead and replace our mock instance. So let's go ahead and replace our instance with one generated by mockgen.
To do so, let's first go ahead and copy the go install command into our clipboard and paste it into the terminal as follows:
go install go.uber.org/mock/mockgen@latest
In my case, I already have it installed, so it's not installing anything, but you should see some outputs letting you know that it's been downloaded and installed. Now, if I go ahead and run the mockgen --version
flag, you would see it's installed of 0.5.2. So yours should be the same or higher.
Now that we have it installed, we can then go about running it, of which there are two different ways to run mockgen:
- Source mode, which works against a source file
- Package mode, which is good when you're working with packages where you may not have the source file available, such as, as you can see, kind of the database SQL driver packages, or even things such as like the IO packages, or any other interface that you want to be able to generate from that you don't have access to the source file to
In our case, we have access to the source file, so we're going to use the source mode, which in this case is to just go ahead and use mockgen with the source and then pointing it to the source file. In our case, that's going to be posts.go
:
mockgen -source=posts.go
As you can see, by running this, it's generated a bunch of code, which is what we can use for our expectations or for our mocking. One thing to note is this has generated this in a package called mock_handler
, and it's printed it to stdout.
We can actually define the package, so we could specify this as kind of handler
if we want to keep it inside of the same code, and you can see it's now inside a package handler, or you could specify this or you can write this out to another package called mock_handler
. Keeping this in another package, in my opinion, is the cleaner approach to do so.
So let's go ahead and do this. To do so, let's go ahead and create a new directory called mock_handler
:
mkdir handler/mock_handler
As you can see, we now have the mock_handler
package inside of the handler package or mock_handler
directory inside of the handler directory. And let's go ahead and create, run the mockgen package, and you could either pipe this out as output, or you can use the destination flag and specify this as mock_handler
:
mockgen -source=posts.go -destination=mock_handler/posts.go
And then we want to specify this as posts.go
because it's the file that's been created. It doesn't have, it won't have any of the posts stuff inside. It would just have the kind of text editor. So you could also do something like text_editor.go
if you want to just specify it as the name. I'm specifying this as the same file that it was generated from.
In any case, with that, we now have the mock handler code inside. As you can see, the editor is complaining. We need to go ahead and use go mod tidy
in order to download the GoMock package. And once that's done, the editor should no longer be complaining.
Using the Generated Mock
Okay, so now that we have our mock code generated into our project, how do we go about using it. Well, if we head on over to the posts test, the first thing we need to do is to set up a mock controller. This basically will handle all of the failures for us out of the box. So we don't need to run any assertions by hand.
To do so, I like to call this the ctrl
. And we can go ahead and use gomock
, which is the go.uber.org/mock/gomock.NewController
. And this takes the type of testing.T
as its parameter:
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Then we can go ahead and do a defer on this and call the defer ctrl.Finish
, which will run at the end of the function. Let's go ahead and do set up mock controller, which will run at the end of the function to make sure any calls that were expected and weren't executed this will just throw an error on our test.
Okay, now that we have the mock controller defined, let's go ahead and actually replace our mock editor with the new one that we created. We can do this by using the mock_handler
package and then creating a NewMockTextEditor
passing in the controller:
editor := mock_handler.NewMockTextEditor(ctrl)
Pretty easy.
Okay, before we do any more configuration, let's go ahead and see what happens if we run this code using the go test
command as follows. This time you can see we're getting a failure because we had an unexpected call to our mock instance, the output string function, with these parameters. This failure is happening because we didn't set any expectation that we wanted a function or a method to be called inside of our mock.
So let's go ahead and fix this. We could do editor.EXPECT()
. And then we can go ahead. We're expecting the OutputString
method to be called. And we can go ahead and pass the arguments we want. For the moment, let's go ahead and do gomock.Any()
. This means we're specifying any arguments to be passed in, but we can constrain this to specific arguments, which we'll take a look at very shortly:
editor.EXPECT().OutputString(gomock.Any(), gomock.Any(), gomock.Any()).Return("my test blog post", nil)
Okay, now if we go ahead and test this, everything should be passing. Almost. We're actually getting back different values. The expectation that we're being called is no longer being valid, but our content is not correct.
So let's go ahead and actually return the content that we're expecting, which is "my test blog post". To do this, we can just go ahead and call this. GoMock uses a fluent API. So you're able to just chain methods on it. So in this case, let's go ahead and call the Return
, which specifies the return values that we want to return. The first one is going to be a "my test blog post". And the second one, we'll just go ahead and return a nil error. Again, we could specify this as a different error if we wanted to. But for this case, we're just specifying that the call or this call to the output string method will return this value.
One thing that's really cool about this is we're specifying that this call with any values will return this. So if we had multiple calls, we could actually constrain these to be like different values as well. But for the meantime, let's just go ahead and leave this as this and everything should now work, which it does. Pretty cool.
Constraining Input Parameters
Of course, we can go ahead and actually constrain that we are expecting different inputs to take place. So in this case, we're actually going to expect the input of "my test blog post". We're expecting the slug to be passed in, which if we go ahead and run, you can see is the case:
editor.EXPECT().OutputString(gomock.Any(), "my-test-blog-post", gomock.Any()).Return("my test blog post", nil)
But if we had a bug and we were expecting something different, now if I go ahead and run this, you can see this time we're getting a failure. "Unexpected call. We expected a call to got a my test blog post, but we expected my test blog post to" or whatever. We set that up as a failure just to demonstrate it. But as you can see, it's really useful for being able to specify further expectations on what you want for the function or the method to be called with.
We can also translate this to other fields as well. So in this case, we want to do a byte slice and we're expecting this to be "my test blog post" with newline, newline, I believe:
editor.EXPECT().OutputString(gomock.Any(), "my-test-blog-post", []byte("my test blog post\n\n")).Return("my test blog post", nil)
And if we go ahead and test this, you can see it's now correct. If we had just one newline, this would also fail as well. It's a little bit harder to see this when it's a slice of bytes. This can be a bit of a pain to read, but kind of once you get the hang of it, you can understand, okay, we got this. What's the difference between this. You can see it's 10 and 10. So you can add like specific methods to print this out a little nicer. But in this case, you can see it's now working as we saw before.
Interface Changes and Regeneration
Okay, now that we've managed to use GoMock, we can go ahead and actually change this by removing this value. However, one thing to consider is that GoMock won't automatically rebuild code if you go ahead and change your values. So for example, if we go ahead and change our editor, let's go ahead to the post handler, change the interface for a text editor by adding in the second property or the second method, which I think is outputs in this case. What is it. Open
. It's the Open
method.
If I go ahead and actually open up a new tab and we check this inside of the editor.editor
, you can see here, yeah, we have the Open
method as well. Open
method returns an os.File
. Sorry. So we need to go ahead and change this to be a pointer to os.File
:
type TextEditor interface {
OutputString(context.Context, string, []byte) (string, error)
Open() (*os.File, error)
}
Okay. So you can see here now everything is working fine because we've specified another method that the text editor provides, even though we're not using it. So we wouldn't do this in production. But if we go to our actual tests now, so we can go to the posts test, you can see this time our editor no longer works. Our mock is no longer working.
So we would need to go ahead and actually generate the code yet again by using mockgen and then just passing it the same command as we did before:
mockgen -source=posts.go -destination=mock_handler/posts.go
Now if I go ahead and run this again, it should actually work. LSP restart is sometimes needed. For some reason it's not working. Let's go ahead and take a quick test. Yeah, no, it is working. It's just my editor is complaining for no good reason.
As you can see, running this command again can be a little bit tedious when it comes to working with other people. Instead, it would be really nice to be able to define this command once and then you can just easily generate it knowing that you generated it using the same command as before.
Using go:generate
This is where another really useful feature of Go comes in. Sorry, this lesson is very long, but there's so much to cover and it's worth doing it in one lesson, in my opinion. But yeah, this is where another kind of Go directive comes in handy, which is known as the go:generate
directive.
This directive is used with the go generate
command, which allows us to easily specify commands that can be used for generating code. If we take a look at the following blog post, it talks about the go generate
command as a directive. I'll leave a link to this in the description down below, but it makes you basically, it makes it very simple to define generated or commands that will generate code and specify them inside of your Go file.
Then rather than having to run the MockGen command that we were doing before, we can just go ahead and use the go generate
command and it should work. Let's go ahead and actually remove the MockHandler:
//go:generate mockgen -source=posts.go -destination=mock_handler/posts.go
And if we run go generate
, you can see the MockHandler is now there and it has the exact same properties that we specified before because we've left this in our code. This also has the benefit of, let's go ahead and remove this again. And if we're inside the root of our directory, we can use go generate ./...
. And this will run the go generate
found in any files inside of our code.
If we do go ahead and do the Handler, you can see the MockHandler is there yet again. This means when it comes to Git, you can also specify this in a way so that you're not committing generated code, which is something you shouldn't do. However, in this case, I would go ahead and kind of do posts
or .mock.go
. And then inside of my Git Ignore, vim .gitignore
, I would just go ahead and kind of any .mock.go
file, I would go ahead and make sure it's added to Git Ignore:
*.mock.go
If we go ahead and do a git status
now, you can see, let's go ahead and add in the .gitignore
. Oh yeah, we need to go ahead and remove this. Let's go ahead and rm -r
the handler.mock_handler
. We'll call go generate
again. This time, if I do a git status
, you can see that it's no longer being added. The Mock, if I go ahead and do ls handler
, mock_handler
, you can see this is posts.mock.go
. This is no longer being kind of added to the untracked files list. So there's no chance of us accidentally committing this.
But we now have our code being generated anytime we run go generate
command, and we don't even have to remember the command to do it.
Mock Validation
As you can see, by using MockGen, it allows us to easily create mocks for certain user interactions when it comes to the CLI application, which is really useful. But you can also create it for things like the database as well, if you really want to. So we could go ahead and create like a database implementation interface. Although when it comes to SQLC, this is done for you. So you would just generate a mock based on SQLC.
However, in my case, I would prefer to kind of create a repo. So this would be a repository type. So type Repository
, and then you could just kind of use this instead of the actual database query, et cetera, et cetera. Although I do think testing with a real database has some benefits compared to not.
In any case, that covers how we can use the GoMock package with MockGen in order to create mocks that simplify our user interaction. And we also looked at a few other concepts as well in this lesson, such as dependency injection, either through a hard dependency, as we're doing with the kind of NewPosts
function with a db.sql
, or by using the option pattern to specify kind of optional based injection. So we set a default here, which is going to be the default text editor, but we made use of the option pattern in order to create functions that we can use to overwrite internal properties without exposing those properties as well.
Summary of Concepts Covered
In addition to dependency injection, we took a look at creating our own interface, which in this case, we defined the TextEditor
interface. Let's go ahead and get rid of this. We don't need this anymore. And let's go ahead and use the go generate
command again, and everything should work as it did before. Great. Let's go ahead and test that. It is the case by using the go test
command. And we want to do, we can do all this here. Test all of the files. The handler should pass as it does. Also, Slugify should pass as well. Pretty great.
So yeah, we looked at interfaces, how we can define an interface to decouple our code from a concrete implementation, which allows us to mock it. And then we looked at using mockgen, which is actually very simple when you get the hang of it, in order to be able to create a simple mock, which will allow us to test whether or not something is called or something is expected.
By the way, the really nice thing about mockgen is this will also fail tests if we don't call the expectation, just to show this in action. If I head on over to my create function, by the way, if this lesson is too long or too complex, let me know in the feedback and I will consider breaking it out as well. There's a lot of information to take in here, and I could really actually split this out if it makes a lot of sense. So split it into kind of mocking, dependency injection, et cetera, et cetera. It just makes sense to add all of this in one go, however.
Okay, let's get rid of the content here, and we'll just kind of hard code this content to be the title, let's say. Actually, we can do kind of here. So yeah, let's do a fmt.Sprintf
and we'll set this as the title. This is exactly kind of what we would expect this to be. But let's say you're being lazy and you just kind of set in a stub method where you don't want to go ahead and implement the code. And then you accidentally forget that you've done this and you go about setting up your mock and everything else.
Now, if I go ahead and test this, it should fail because "missing calls to mock_handler". As you can see, we expected a call and it never came through. This is where mockgen is really useful because it not only checks to make sure that you're calling the function or that you've specified you're expecting a call, but if you do define an expectation and you don't call it, then there you're also getting a failure as well.
As I said, I really like mockgen. It's very useful for creating mocks, although knowing when to create a mock is a bit of a skill and a bit of an art.
Homework and Next Steps
With that, we've taken a look at how we can add in mocks to some of the more user interface-based functionality of our code. In the next lesson, we're going to take a look at another final third-party package or dependency called GoReleaser, which we can use to both build and release our applications.
As for homework, I recommend adding in mocks to any user interaction-based code you have in your own CMS system. Other than that, however, your CMS system at this point should be in a really good spot and you should have kind of your own ability to manage the content that you've defined within.
So I'll see you in the next lesson when we'll take a look at GoReleaser.
No homework tasks for this lesson.