I am learning Golang and was working on an application that reads a yaml configuration file and loads its contents into a struct. I'm adding tests to the app, and was wondering if there is a way of no having to pass a real file to ioutil.ReadFile and instead mock its contents.
Let's say that the struct object of the configuration is something like:
type AppConfig struct {
someConfig string `yaml:"someConfig"`
someOtherConfig string `yaml:"someOtherConfig"`
}
And the function to read the configuration is:
func readConfig(filePath string) (*AppConfig, error) {
file, err := ioutil.ReadFile(filePath)
if err != nil {
log.Fatal(err)
}
conf := AppConfig{}
err = yaml.Unmarshal([]byte(file), &conf)
if err != nil {
return &AppConfig{}, err
}
fmt.Printf("\n%#v\n", conf)
return &conf, nil
}
I would split the function into one that does the reading and one that does Unmarshalling into conf. Pass the result of the first function (the file contents) into the second. Then you can easily test the second function.
Another option would be to wrap ioutil.ReadFile(filePath) into a helper function and mock that function when testing readConfig().
It can be done easily by the use of interface and a little modification.
// Defining an interface so that functionality of 'readConfig()' can be mocked
type IReader interface{
readConfig() ([]byte, error)
}
type reader struct{
fileName string
}
// 'reader' implementing the Interface
// Function to read from actual file
func (r *reader) readConfig() ([]byte, error) {
file, err := ioutil.ReadFile(r.fileName)
if err != nil {
log.Fatal(err)
}
return file, err
}
Modifying the original code to read and set config:
type AppConfig struct {
someConfig string `yaml:"someConfig"`
someOtherConfig string `yaml:"someOtherConfig"`
}
// Function takes the mentioned Interface as a parameter
func getConfig(reader IReader) (*AppConfig, error) {
file, err :=reader.readConfig()
conf := AppConfig{}
err = yaml.Unmarshal(file, &conf)
if err != nil {
return &AppConfig{}, err
}
fmt.Printf("\n%#v\n", conf)
return &conf, nil
}
And when you want to read using actual method:
func main() {
reader := reader{fileName:"Actual File Name"}
configVal, err := getConfig(&reader)
fmt.Println("Values received from file: ", configVal, err)
}
Now, coming to testing the code:
type readerTest struct {
fileName string
}
// 'readerTest' implementing the Interface
func (r *readerTest) readConfig() ([]byte, error) {
// Prepare data you want to return without reading from the file
return []byte{}, nil
}
func TestGetConfig() {
testReader := readerTest{fileName:"Sample File Name"}
configVal, err := getConfig(&testReader)
fmt.Println("Write tests on values: ", configVal, err)
}
Related
I have created a function that utilizes the grpc package in golang. I don't know if it is relevant but the purpose is the communication with a GoBGP router over grpc. An example is the following function which prints all the peers (neighbors) of the router:
func (gc *Grpc) Peers(conn *grpc.ClientConn) error {
defer conn.Close()
c := pb.NewGobgpApiClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
p := pb.ListPeerRequest{}
peer, err := c.ListPeer(ctx, &p)
if err != nil {
return err
}
for {
res, err := peer.Recv()
if err != nil {
return err
}
fmt.Println(res)
}
return nil
}
Now, I want to create unit tests for the function. To do so, I used google.golang.org/grpc/test/bufconn package, and initialized the following:
type server struct {
pb.UnimplementedGobgpApiServer
}
func (s *server) ListDefinedSet(in *pb.ListDefinedSetRequest, ls pb.GobgpApi_ListDefinedSetServer) error {
return nil
}
var lis *bufconn.Listener
const bufSize = 1024 * 1024
func init() {
lis = bufconn.Listen(bufSize)
s := grpc.NewServer()
pb.RegisterGobgpApiServer(s, &server{})
go func() {
if err := s.Serve(lis); err != nil {
fmt.Println("Server failed!")
}
}()
}
func bufDialer(context.Context, string) (net.Conn, error) {
return lis.Dial()
}
This way, I can run a unit-test creating a connection as follows:
ctx := context.Background()
conn, _ := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
Peers(conn)
However, the problem is that the stream seems to be always empty and thus the peer.Recv()
always returns EOF. Is there any way to populate the stream with dummy data? If you have experience, is my methodology correct?
I've been trying to write unit tests for my http handler. The code segment is as below:
func (s *Server) handleCreateTicketOption(w http.ResponseWriter, r *http.Request) {
var t ticket.Ticket
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
err = json.Unmarshal(body, &t)
if err != nil {
http.Error(w, er.ErrInvalidData.Error(), http.StatusBadRequest)
return
}
ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
res, err := json.Marshal(ticket)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
log.Printf("%v tickets allocated with name %v\n", t.Allocation, t.Name)
s.sendResponse(w, res, http.StatusOK)
}
Actual logic that interacts with DB. This code segment is invoked by the handler as you can see in the code above. ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
func (t *TicketService) CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
tx, err := t.db.dbPool.Begin(ctx)
if err != nil {
return nil, er.ErrInternal
}
defer tx.Rollback(ctx)
var id int
err = tx.QueryRow(ctx, `INSERT INTO ticket (name, description, allocation) VALUES ($1, $2, $3) RETURNING id`, ticket.Name, ticket.Description, ticket.Allocation).Scan(&id)
if err != nil {
return nil, er.ErrInternal
}
ticket.Id = id
return &ticket, tx.Commit(ctx)
}
And that is my unit test for the handler.
func TestCreateTicketOptionHandler(t *testing.T) {
caseExpected, _ := json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 10})
srv := NewServer()
// expected := [][]byte{
// _, _ = json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20}),
// // json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20})
// }
tt := []struct {
name string
entry *ticket.Ticket
want []byte
code int
}{
{
"valid",
&ticket.Ticket{Name: "baris", Description: "test-desc", Allocation: 10},
caseExpected,
http.StatusOK,
},
}
var buf bytes.Buffer
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
json.NewEncoder(&buf).Encode(tc.entry)
req, err := http.NewRequest(http.MethodPost, "/ticket_options", &buf)
log.Println("1")
if err != nil {
log.Println("2")
t.Fatalf("could not create request: %v", err)
}
log.Println("3")
rec := httptest.NewRecorder()
log.Println("4")
srv.handleCreateTicketOption(rec, req)
log.Println("5")
if rec.Code != tc.code {
t.Fatalf("got status %d, want %v", rec.Code, tc.code)
}
log.Println("6")
if reflect.DeepEqual(rec.Body.Bytes(), tc.want) {
log.Println("7")
t.Fatalf("NAME:%v, got %v, want %v", tc.name, rec.Body.Bytes(), tc.want)
}
})
}
}
I did research about mocking pgx about most of them were testing the logic part not through the handler. I want to write unit test for both handler and logic itself seperately. However, the unit test I've written for the handler panics as below
github.com/bariis/gowit-case-study/psql.(*TicketService).CreateTicketOption(0xc000061348, {0x1485058, 0xc0000260c0}, {0x0, {0xc000026dd0, 0x5}, {0xc000026dd5, 0x9}, 0xa})
/Users/barisertas/workspace/gowit-case-study/psql/ticket.go:24 +0x125
github.com/bariis/gowit-case-study/http.(*Server).handleCreateTicketOption(0xc000061340, {0x1484bf0, 0xc000153280}, 0xc00018e000)
/Users/barisertas/workspace/gowit-case-study/http/ticket.go:77 +0x10b
github.com/bariis/gowit-case-study/http.TestCreateTicketOptionHandler.func2(0xc000119860)
/Users/barisertas/workspace/gowit-case-study/http/ticket_test.go:80 +0x305
psql/ticket.go:24: tx, err := t.db.dbPool.Begin(ctx)
http/ticket.go:77: ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
http/ticket_test.go:80: srv.handleCreateTicketOption(rec, req)
How can I mock this type of code?
Create an interface which has the required DB functions
Your DB handler implements this interface. You use the handler in actual execution
Create a mock handler using testify/mock and use this in place of DB handler in test cases
From what I can read, you have the following structure:
type Server struct {
TicketService ticket.Service
}
type TicketService struct {
db *sql.Db // ..or similar
}
func (ts *TicketService) CreateTicketOption(...)
The trick to mock this is by ensuring ticket.Service is an interface instead of a struct.
Like this:
type TicketService interface {
CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
}
By doing this, your Server expects a TicketService interface.
Then you could do this:
type postgresTicketService struct {
db *sql.Db
}
func (pst *postgresTicketService) CreateTicketOption(...)...
Which means that the postgresTicketService satisfies the requirements to be passed as a ticket.Service to the Server.
This also means that you can do this:
type mockTicketService struct {
}
func (mts *mockTicketService) CreateTicketOption(...)...
This way you decouple the Server from the actual implementation, and you could just init the Server with the mockTicketService when testing and postgresTicketService when deploying.
I am new to golang and I am using an interactive prompt called promptui (https://github.com/manifoldco/promptui) in a project of mine. I have written several unit tests for this project already but I am struggling with how I would unit test this particular package that requires an input.
For example, How would I go about testing the following lines of code (encapsulated in a function):
func setEmail() string {
prompt := promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
email, err := prompt.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}
I think I need to somehow mock stdin but can't figure out the best way to do that within a test.
You should not try to test promptui as it is expected to be tested by its author.
What you can test:
You send correct parameters when you create promptui.Prompt
You use that promptui.Prompt in your code
You properly handle promptui.Prompt results
As you can see, all these tests does not verify if promptui.Prompt works correctly inside.
Tests #2 and #3 could be combined. You need to run you code against mock and if you got correct result, you can believe that both #2 and #3 are correct.
Create mock:
type Runner interface {
Run() (int, string, error)
}
type promptMock struct {
// t is not required for this test, but it is would be helpful to assert input parameters if we have it in Run()
t *testing.T
}
func (p promptMock) Run() (int, string, error) {
// return expected result
return 1, "", nil
}
You will need separate mock for testing error flow.
Update your code to inject mock:
func setEmail(runner Runner) string {
email, err := runner.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}
Now it is testable.
Create function that creates prompt:
func getRunner() promptui.Prompt {
return promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
}
Write simple assert test to verify that we create correct structure.
The only not tested line will be setEmail(getRunner()) but it is trivial and can be covered by other types of tests.
For whatever reason, they don't export their stdin interface (https://github.com/manifoldco/promptui/blob/master/prompt.go#L49), so you can't mock it out, but you can directly mock os.Stdin and prefill it with whatever you need for testing. Though I agree with #Adrian, it has its own tests, so this shouldn't be necessary.
Extracted and refactored/simplified from source: Fill os.Stdin for function that reads from it
Refactored this way, it can be used for any function that reads from os.Stdin and expects a specific string.
Playground link: https://play.golang.org/p/rjgcGIaftBK
func TestSetEmail(t *testing.T) {
if err := TestExpectedStdinFunc("email#test.com", setEmail); err != nil {
t.Error(err)
return
}
fmt.Println("success")
}
func TestExpectedStdinFunc(expected string, f func() string) error {
content := []byte(expected)
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}
defer os.Remove(tmpfile.Name()) // clean up
if _, err := tmpfile.Write(content); err != nil {
return err
}
if _, err := tmpfile.Seek(0, 0); err != nil {
return err
}
oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin
os.Stdin = tmpfile
actual := f()
if actual != expected {
return errors.New(fmt.Sprintf("test failed, exptected: %s actual: %s", expected, actual))
}
if err := tmpfile.Close(); err != nil {
return err
}
return nil
}
promptui now has the Stdin property.
There is a fiddle here: https://play.golang.org/p/-mSgjY2kAw-
Here is our function that we will be testing:
func mock(p promptui.Prompt) string {
p.Label = "[Y/N]"
user_input, err := p.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
}
return user_input
}
We need to create p, which will be an instance of promptui.Prompt and have a custom Stdin.
I got some help here - https://groups.google.com/g/golang-nuts/c/J-Y4LtdGNSw?pli=1 - in how to make a custom Stdin value, which simply has to conform to io.ReadCloser.
type ClosingBuffer struct {
*bytes.Buffer
}
func (cb ClosingBuffer) Close() error {
return nil
}
And then you use that as Stdin in the reader:
func TestMock(t *testing.T) {
reader := ClosingBuffer{
bytes.NewBufferString("N\n"),
}
p := promptui.Prompt{
Stdin: reader,
}
response := mock(p)
if !strings.EqualFold(response, "N") {
t.Errorf("nope!")
}
//t.Errorf(response)
}
edit: The above doesn't work for multiple prompts within the same function, as discussed here with a solution: https://github.com/manifoldco/promptui/issues/63 - "promptui internally uses a buffer of 4096 bytes. This means that you must pad your buffer or promptui will raise EOF."
I took this pad() function from that exchange - https://github.com/sandokandias/capiroto/blob/master/cmd/capiroto/main.go:
func pad(siz int, buf *bytes.Buffer) {
pu := make([]byte, 4096-siz)
for i := 0; i < 4096-siz; i++ {
pu[i] = 97
}
buf.Write(pu)
}
Then the test - - this solution uses ioutil.NopCloser rather than creating a new struct:
func TestMock(t *testing.T) {
i1 := "N\n"
i2 := "Y\n"
b := bytes.NewBuffer([]byte(i1))
pad(len(i1), b)
reader := ioutil.NopCloser(
b,
)
b.WriteString(i2)
pad(len(i2), b)
p := promptui.Prompt{
Stdin: reader,
}
response := mock(p)
if !strings.EqualFold(response, "NY") {
t.Errorf("nope!")
t.Errorf(response)
}
}
and the function we are testing:
func mock(p promptui.Prompt) string {
p.Label = "[Y/N]"
user_input, err := p.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
}
user_input2, err := p.Run()
return user_input + user_input2
}
The fiddle for multiple prompts is here: https://play.golang.org/p/ElPysYq8aM1
I have a program in go that connects to AWS S3 and gets a file.
I'd like to write some tests for it, but I'd like to know, more generally, how to do these mocks in Golang. I know there are some libraries to create mocks but if I remember correctly I read someone suggesting using only standard libraries for unit tests was the best way to go.
So, how would you test a function like this?
func (s S3Input) Sample(key string) ([]byte, error) {
var buf []byte
waBuf := aws.NewWriteAtBuffer(buf)
_, err := s.Downloader.Download(
waBuf,
&s3.GetObjectInput{
Bucket: aws.String(s.Bucket),
Key: aws.String(key),
},
)
if err != nil {
return nil, err
}
return buf, nil
}
Thank you!
One way to do it is to inject the dependencies in your structure, like such:
type S3Inputer interface {
NewWriteAtBuffer(buf []byte) *aws.WriteAtBuffer
String(v string) *string
}
type S3Input struct {
newWriteAtBufferFunc func(buf []byte) *aws.WriteAtBuffer
stringFunc func(v string) *string
}
func (s *S3Input) NewWriteAtBuffer(buf []byte) *WriteAtBuffer {
return s.newWriteAtBufferFunc(buf)
}
func (s *S3Input) String(v string) *string {
return s.stringFunc(v)
}
func (s S3Input) Sample(key string) ([]byte, error) {
var buf []byte
waBuf := s.NewWriteAtBuffer(buf)
_, err := s.Downloader.Download(
waBuf,
&s3.GetObjectInput{
Bucket: s.String(s.Bucket),
Key: s.String(key),
},
)
if err != nil {
return nil, err
}
return buf, nil
}
func main() {
s := &S3Input{
StringFunc: aws.String,
NewWriteAtBufferFunc: aws.NewWriteAtBuffer,
}
// ...
}
This allows you to replace those functions with whatever you want for testing, without the need of any testing framework.
Then, the testing function would look something like this:
func (s S3Input) TestSample(t *testing.T) {
s3Mock := &S3Input{
StringFunc: (func (v string) *string {
return nil
}),
NewWriteAtBufferFunc: (func (buf []byte) *aws.WriteAtBuffer {
return nil
}),
}
res, err := s3Mock.Sample(...) //
// asserts & error checks
}
You could improve it by creating a S3InputMock type instead of reusing the base one, both would implement the S3Inputer interface and your mock could have attributes allowing it to help you with testing. For example, it could count the number of times a function is called, store the arguments it received, have its methods behave differently depending on the attributes you set for easier testing, etc.
In the package I want to test, I have an init function that loads the configuration file containing some stuff I want to use to run my application. However, I don't want to trigger this init function while running my unit tests.
Is there any way for skipping or preventing this init function to be called during the unit tests?
Some snippets to illustrate the question:
func init() {
var err error // Necessary to prevent config variable shadowing
config, err = loadConfig("./client/config.yml")
if err != nil {
log.Fatal(err)
}
}
func loadConfig(filepath string) (*Config, error) {
viper.SetConfigFile(filepath)
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("Error loading config file: %s", err)
}
(...)
}
// New returns a Config value(!)
func New() Config {
return *config
}
A test case:
func TestNew(t *testing.T) {
expected := &Config{}
observed := New()
if !reflect.DeepEqual(observed, expected) {
t.Errorf("observed %+v. expecting %+v\n", observed, expected)
}
}
I'm not sure whether there's a nicer way of doing this, but if you consider the fact that package-level variables are initialized before the init func is run you can use a flag to tell you whether you're running tests or not.
var _testing = false
func init() {
if _testing {
return
}
var err error // Necessary to prevent config variable shadowing
config, err = loadConfig("./client/config.yml")
if err != nil {
log.Fatal(err)
}
}
// ...
And in your test file you could do something like this:
// not nice but works
var _ = (func() interface{} {
_testing = true
return nil
}())
func TestNew(t *testing.T) {
expected := &Config{}
observed := New()
if !reflect.DeepEqual(observed, expected) {
t.Errorf("observed %+v. expecting %+v\n", observed, expected)
}
}
You can read more on the initialization order here: https://golang.org/ref/spec#Program_initialization_and_execution