So I have a web app running in serverless. It spins up a bunch of lambdas and then a 'test' lambda is invoked at a later stage of our pipeline to run some api tests against the other lambbas. It passes or fails code pipeline depending on the result of the tests.
My concern is the implementation of the tests portion itself.
We're using golang, and I wasn't able to successfully find a way to have a bunch of go test files and run them, record the results and determine pass/fail or not.. but I wanted to use the test suite and library in order to run my assertions. So I came up with this solution which was to break about the go test library and run it myself using MainStart(), ex:
// T is used to manage our test cases manually using MainStart
type T struct{}
func (*T) ImportPath() string { return "" }
func (*T) MatchString(pat, str string) (bool, error) { return true, nil }
func (*T) SetPanicOnExit0(bool) {}
func (*T) StartCPUProfile(io.Writer) error { return nil }
func (*T) StopCPUProfile() {}
func (*T) StartTestLog(io.Writer) {}
func (*T) StopTestLog() error { return nil }
func (*T) WriteHeapProfile(io.Writer) error { return nil }
func (*T) WriteProfileTo(string, io.Writer, int) error { return nil }
func (h *Handler) Handle(ctx context.Context, event events.CodePipelineEvent) (interface{}, error) {
job := event.CodePipelineJob
ok := runTest()
if ok {
logger.Info("Tests Passed!")
input := &codepipeline.PutJobSuccessResultInput{
JobId: &job.ID,
success, err := h.service.PutJobSuccessResult(input)
return success, err
} else {
logger.Info("Tests Failed :(")
input := &codepipeline.PutJobFailureResultInput{
JobId: &job.ID,
FailureDetails: &codepipeline.FailureDetails{
Message: aws.String("tests failed"),
Type: aws.String("JobFailed"),
},
failure, err := h.service.PutJobFailureResult(input)
return failure, err
}
}
}
// NewHandler returns a pointer to a Handler struct
func NewHandler(service *codepipeline.CodePipeline) *Handler {
return &Handler{
service,
}
}
// runTest returns the test results
func runTest() bool {
testSuite := []testing.InternalTest{
{Name: "Test Service", F: TestService},
}
logger.Info("Running Tests")
errors := testing.MainStart(&T{}, testSuite, nil, nil).Run()
if errors == 0 {
return true
} else {
return false
}
}
So the test looks like this:
func TestServices(t *testing.T) {
logger.Info("blah blah do tests")
}
And for reference, main.go contains:
// Initial code pipeline session
cpSession := session.Must(session.NewSession())
// Create the credentials from AssumeRoleProvider to assume the role
// referenced by the "putJobResultRoleArn" ARN.
creds := stscreds.NewCredentials(cpSession, "putJobResultRoleArn", func(p *stscreds.AssumeRoleProvider) {
p.RoleARN = roleArn
p.RoleSessionName = "put_job_result_session"
})
// Create a new instance of the CodePipeline client with a session
svc := codepipeline.New(cpSession, &aws.Config{Credentials: creds})
intTesthandler := inttest.NewHandler(
svc,
)
lambda.Start(func(ctx context.Context, event events.CodePipelineEvent) (interface{}, error) {
return intTesthandler.Handle(ctx, event)
})
This works quite well, but what I don't like is:
It's using an unsupported approach from golang that could break in the future
I have to handhold any failures, meaning if one fails, I need to write code to stop the rest of the suite (if there were more tests) from running.
Bit of a learning curve
To note the tests I have mainly do an http request to an api endpoint and validate the response + parse/store the data.
Anyone have any other creative solution? Is there a repo out there that makes it simple to run integration tests in golang?
Cheers
Related
I recently wanted to try out dependency injection in golang and I picked up Uber dig container everything working well but I am not sure how to test my code.
My dig container
func buildContainer() *dig.Container {
container := dig.New()
_ = container.Provide(config.GetInstance)
_ = container.Provide(database.GetInstance)
_ = container.Provide(evaluation.NewEvaluator)
_ = container.Provide(repository.NewExperimentRepository)
return container
}
My Dao
type ExperimentDao interface {
// Create experiment
Create(experiment *models.Experiment) error
// Update experiment
Update(experiment *models.Experiment) error
// FindByID find experiment by id
FindByID(id string) (*models.Experiment, error)
//FindAll find all the questions
FindAll(filter bson.M, page int, limit int) (*models.ExperimentPagination, error)
}
My Repository that implements the doa interface
type ExperimentRepository struct {
DB *database.DB
}
// NewExperimentRepository returns new ExperimentDao
func NewExperimentRepository(db *database.DB) interfaces.ExperimentDao {
return &ExperimentRepository{DB: db}
}
func (expRepo *ExperimentRepository) Create(experiment *models.Experiment) error {
//repository code to insert in DB
}
// rest of the implemented func(s)
Here is my handler where I use the injected Repository to insert the data
func CreateExperiment(res http.ResponseWriter, req *http.Request) {
experiment := models.Experiment{}
err := json.NewDecoder(req.Body).Decode(&experiment)
if err != nil {
logrus.Error("Create Experiment Error parsing payload : ", err)
utils.RespondError(res, err, 400)
return
}
ValErr := ValidateExpt(experiment, "create")
if ValErr != "" {
logrus.Error("Create Experiment Error : ", ValErr)
utils.Respond(res, ValErr, 400)
return
}
di := container.GetInstance()
err = di.Invoke(func(dao interfaces.ExperimentDao) {
err := dao.Create(&experiment)
if err != nil {
utils.RespondError(res, err, http.StatusInternalServerError)
return
}
utils.Respond(res, "experiment created successfully", http.StatusOK)
})
if err != nil {
logrus.Error("Insert Experiment Error : ", err)
utils.RespondError(res, err, 400)
}
}
// Di builder
func GetInstance() *dig.Container {
once.Do(func() {
container = buildContainer()
})
return container
}
Now as the code is working fine as expected so I hope the approach is correct as per my knowledge but now I am unable to figure out how to write the test to test this code I can write few validations for experiment model but not for repository, dao or the di container.
Can you guys help where should I look or what am I missing.
Thanks.
I would suggest you to write a function, that will receive all dependencies as parameters and will return the HTTP handler, like this:
func NewCreateExperimentHandler(c *dig.Container) http.HandlerFunc {
return func (res http.ResponseWriter, req *http.Request) {
// the current CreateExperiment() implementation
}
}
This will allow you to mock your container inside the tests.
I am leaning to write unit tests and I was wondering the correct way to unit test a basic http.get request.
I found an API online that returns fake data and wrote a basic program that gets some user data and prints out an ID:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type UserData struct {
Meta interface{} `json:"meta"`
Data struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Gender string `json:"gender"`
Status string `json:"status"`
} `json:"data"`
}
func main() {
resp := sendRequest()
body := readBody(resp)
id := unmarshallData(body)
fmt.Println(id)
}
func sendRequest() *http.Response {
resp, err := http.Get("https://gorest.co.in/public/v1/users/1841")
if err != nil {
log.Fatalln(err)
}
return resp
}
func readBody(resp *http.Response) []byte {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
return body
}
func unmarshallData(body []byte) int {
var userData UserData
json.Unmarshal(body, &userData)
return userData.Data.ID
}
This works and prints out 1841. I then wanted to write some tests that validate that the code is behaving as expected, e.g. that it correctly fails if an error is returned, that the data returned can be unmarshalled. I have been reading online and looking at examples but they are all far more complex that what I feel I am trying to achieve.
I have started with the following test that ensures that the data passed to the unmarshallData function can be unmarshalled:
package main
import (
"testing"
)
func Test_unmarshallData(t *testing.T) {
type args struct {
body []byte
}
tests := []struct {
name string
args args
want int
}{
{name: "Unmarshall", args: struct{ body []byte }{body: []byte("{\"meta\":null,\"data\":{\"id\":1841,\"name\":\"Piya\",\"email\":\"priya#gmai.com\",\"gender\":\"female\",\"status\":\"active\"}}")}, want: 1841},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := unmarshallData(tt.args.body); got != tt.want {
t.Errorf("unmarshallData() = %v, want %v", got, tt.want)
}
})
}
}
Any advise on where to go from here would be appreciated.
before moving on to the testing, your code has a serious flow, which will become a problem if you don't take care about it in your future programming tasks.
https://pkg.go.dev/net/http See the second example
The client must close the response body when finished with it
Let's fix that now (we will have to come back on this subject later), two possibilities.
1/ within main, use defer to Close that resource after you have drained it;
func main() {
resp := sendRequest()
defer body.Close()
body := readBody(resp)
id := unmarshallData(body)
fmt.Println(id)
}
2/ Do that within readBody
func readBody(resp *http.Response) []byte {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
return body
}
Using a defer is the expected manner to close the resource. It helps the reader to identify the lifetime span of the resource and improve readability.
Notes : I will not be using much of the table test driven pattern, but you should, like you did in your OP.
Moving on to the testing part.
Tests can be written under the same package or its fellow version with a trailing _test, such as [package target]_test. This has implications in two ways.
Using a separate package, they will be ignored in the final build. Which will help to produce smaller binaries.
Using a separate package, you test the API in a black box manner, you can access only the identifiers it explicitly exposes.
Your current tests are white boxed, meaning you can access any declaration of main, public or not.
About sendRequest, writing a test around this is not very interesting because it does too little, and your tests should not be written to test the std library.
But for the sake of the demonstration, and for good reasons we might want to not rely on external resources to execute our tests.
In order to achieve that we must make the global dependencies consumed within it, an injected dependency. So that later on, it is possible to replace the one thing it depends on to react, the http.Get method.
func sendRequest(client interface{Get() (*http.Response, error)}) *http.Response {
resp, err := client.Get("https://gorest.co.in/public/v1/users/1841")
if err != nil {
log.Fatalln(err)
}
return resp
}
Here i use an inlined interface declaration interface{Get() (*http.Response, error)}.
Now we can add a new test which injects a piece of code that will return exactly the values that will trigger the behavior we want to test within our code.
type fakeGetter struct {
resp *http.Response
err error
}
func (f fakeGetter) Get(u string) (*http.Response, error) {
return f.resp, f.err
}
func TestSendRequestReturnsNilResponseOnError(t *testing.T) {
c := fakeGetter{
err: fmt.Errorf("whatever error will do"),
}
resp := sendRequest(c)
if resp != nil {
t.Fatal("it should return a nil response when an error arises")
}
}
Now run this test and see the result. It is not conclusive because your function contains a call to log.Fatal, which in turns executes an os.Exit; We cannot test that.
If we try to change that, we might think we might call for panic instead because we can recover.
I don't recommend doing that, in my opinion, this is smelly and bad, but it exists, so we might consider. This is also the least possible change to the function signature. Returning an error would break even more the current signatures. I want to minimize this for that demonstration. But, as a rule of thumb, return an error and always check them.
In the sendRequest function, replace this call log.Fatalln(err) with panic(err) and update the test to capture the panic.
func TestSendRequestReturnsNilResponseOnError(t *testing.T) {
var hasPanicked bool
defer func() {
_ = recover() // if you capture the output value or recover, you get the error gave to the panic call. We have no use of it.
hasPanicked = true
}()
c := fakeGetter{
err: fmt.Errorf("whatever error will do"),
}
resp := sendRequest(c)
if resp != nil {
t.Fatal("it should return a nil response when an error arises")
}
if !hasPanicked {
t.Fatal("it should have panicked")
}
}
We can now move on to the other execution path, the non error return.
For that we forge the desired *http.Response instance we want to pass into our function, we will then check its properties to figure out if what the function does is inline with what we expect.
We will consider we want to ensure it is returned unmodified : /
Below test only sets two properties, and I will do it to demonstrate how to set the Body with a NopCloser and strings.NewReader as it is often needed later on using the Go language;
I also use reflect.DeepEqual as brute force equality checker, usually you can be more fine grained and get better tests. DeepEqual does the job in this case but it introduces complexity that does not justify systematic use of it.
func TestSendRequestReturnsUnmodifiedResponse(t *testing.T) {
c := fakeGetter{
err: nil,
resp: &http.Response{
Status: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader("some text")),
},
}
resp := sendRequest(c)
if !reflect.DeepEqual(resp, c.resp) {
t.Fatal("the response should not have been modified")
}
}
At that point you may have figured that this small function sendRequest is not good, if you did not I ensure you it is not. It does too little, it merely wraps the http.Get method and its testing is of little interest for the survival of the business logic.
Moving on to readBody function.
All remarks that applied for sendRequest apply here too.
it does too little
it os.Exits
One thing does not apply. As the call to ioutil.ReadAll does not rely on external resources, there is no point in attempting to inject that dependency. We can test around.
Though, for the sake of the demonstration, it is the time to talk about the missing call to defer resp.Body.Close().
Let us assume we go for the second proposition made in introduction and test for that.
The http.Response struct adequately exposes its Body recipient as an interface.
To ensure the code calls for the `Close, we can write a stub for it.
That stub will record if that call was made, the test can then check for that and trigger an error if it was not.
type closeCallRecorder struct {
hasClosed bool
}
func (c *closeCallRecorder) Close() error {
c.hasClosed = true
return nil
}
func (c *closeCallRecorder) Read(p []byte) (int, error) {
return 0, nil
}
func TestReadBodyCallsClose(t *testing.T) {
body := &closeCallRecorder{}
res := &http.Response{
Body: body,
}
_ = readBody(res)
if !body.hasClosed {
t.Fatal("the response body was not closed")
}
}
Similarly, and for the sake of the demonstration, we might want to test if the function has called for Read.
type readCallRecorder struct {
hasRead bool
}
func (c *readCallRecorder) Read(p []byte) (int, error) {
c.hasRead = true
return 0, nil
}
func TestReadBodyHasReadAnything(t *testing.T) {
body := &readCallRecorder{}
res := &http.Response{
Body: ioutil.NopCloser(body),
}
_ = readBody(res)
if !body.hasRead {
t.Fatal("the response body was not read")
}
}
We an also verify the body was not modified in betwen,
func TestReadBodyDidNotModifyTheResponse(t *testing.T) {
want := "this"
res := &http.Response{
Body: ioutil.NopCloser(strings.NewReader(want)),
}
resp := readBody(res)
if got := string(resp); want != got {
t.Fatal("invalid response, wanted=%q got %q", want, got)
}
}
We have almost done, lets move one to the unmarshallData function.
You have already wrote a test about it. It is okish, though, i would write it this way to make it leaner:
type UserData struct {
Meta interface{} `json:"meta"`
Data Data `json:"data"`
}
type Data struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Gender string `json:"gender"`
Status string `json:"status"`
}
func Test_unmarshallData(t *testing.T) {
type args struct {
body []byte
}
tests := []UserData{
UserData{Data: Data{ID: 1841}},
}
for _, u := range tests {
want := u.ID
b, _ := json.Marshal(u)
t.Run("Unmarshal", func(t *testing.T) {
if got := unmarshallData(b); got != want {
t.Errorf("unmarshallData() = %v, want %v", got, want)
}
})
}
}
Then, the usual apply :
don't log.Fatal
what are you testing ? the marshaller ?
Finally, now that we have gathered all those pieces, we can refactor to write a more sensible function and re use all those pieces to help us testing such code.
I won't do it, but here is a starter, which still panics, and I still don't recommend, but the previous demonstration has shown everything needed to test a version of it that returns an error.
type userFetcher struct {
Requester interface {
Get(u string) (*http.Response, error)
}
}
func (u userFetcher) Fetch() int {
resp, err := u.Requester.Get("https://gorest.co.in/public/v1/users/1841") // it does not really matter that this string is static, using the requester we can mock the response, its body and the error.
if err != nil {
panic(err)
}
defer resp.Body.Close() //always.
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
var userData UserData
err = json.Unmarshal(body, &userData)
if err != nil {
panic(err)
}
return userData.Data.ID
}
I am looking to mock a method of a struct in tests to improve code coverage.
There are a few posts regarding this, none of them are working for me. I might have gotten this completely wrong.
main/file1.go
type application struct {
Name string
}
func (app *application) find() error {
// perform function logic
return nil
}
func main() {
app := &application{
Name: "Main Application",
}
err := app.find()
if err != nil {
fmt.Printf("Error in find call: %s\n", err)
}
}
I need to be able to mock test for find() and return an error (I would not want to generate a test case that could result in an error as this is not under my control and I am not sure how to generate one by passing acceptable params).
I tried to follow the second answer from this post and the compiler is not liking it.
main/file1_test.go
func Test_application_find(t *testing.T) {
tests := []struct {
name string
app *application
wantErr string
}{
{
name: "generate error",
app: &application{
Name: "Mock Application",
},
wantErr: true,
},
}
for _, tt := range tests {
mockCaller := tt.app.find // this works fine
tt.app.find = func() error { // this assignment errors out
return fmt.Errorf("Mock Error Message")
}
defer func() {
tt.app.find = mockCaller // this assignment errors out
}()
t.Run(tt.name, func(t *testing.T) {
if err := tt.app.find(); (err != nil) && (err.Error() != "Mock Error Message") {
t.Errorf("error = %s, wantErr %s", err.Error(), tt.wantErr)
}
}
}
}
What am I doing wrong here? Please suggest.
There are several ways to mock a method. They all have situations they are good for.
Composition
For the tests you create a new struct on top of the original.
struct testApp struct{
application
}
Now teststruct behaves almost the same way as app, but you can overwrite the find function:
func (app *testApp) find() error {
return errors.New("some error")
}
In the test you initialize the testApp with a proper app given to it:
app := testApp{
application: &application{
Name: "Mock Application",
}
}
Now you can write your tests on the app variable.
Note that this will not work for all use cases since composition is not inheritance. Calling a function on the original application will not call the new find function in testApp.
Interface
You can also mock the entire application struct / the parts you need for your tests. This mostly makes sense if you want to test another struct with a dependency on application.
The struct to be tested should not use application directly but an interface of the functionality it needs. (This might mean refactoring your code to do so.)
Then create a mock with the mocked find function and pass the mock to the struct to be tested:
type app interface{
find() error
}
type mockApp struct{
Name string
}
(s *mockApp) find() error {
return errors.New("some error")
}
Struct Field
You could also make the find function a struct field. Then you can overwrite it for test purposes. That however has the downside of having to change the code.
type application struct {
Name string
find func() error
}
Write a creator to be able to still initialize the struct in a reasonable fashion:
func newApplication() *application {
return &application{
Name: "Main Application",
find: func() error {
// perform function logic
return nil
}
}
}
Now you can overwrite the find method (actually it is a field now) in your tests:
app := newApplication()
app.find = func() error {
return errors.New("some error")
}
In main.go, I have some code that makes a network call to AWS Secrets manager.
func main() {
secretName := os.Getenv("DYNAMO_SECRET")
credentials, err := getSecret(secretName)
if err != nil {
logger.Errorf("Failed to retrieve secret from AWS Secrets manager %+v\n", err)
panic(err)
}
router, err := setupRouter(credentials)
The getSecret(secretName) function makes a network call to AWS Secrets manager underneath the hood. In my unit test for main, I have the code below.
func TestMainProgram(t *testing.T) {
defer mockStartServer(nil)()
defer mockSetupRouter(mux.NewRouter(), nil)()
main()
t.Log("Everything is perfect.")
}
When running my unit test, I want to mock the network call to AWS Secrets Manager. Is it possible to mock the return value of getSecret(secretName)? In a Java context, I'm trying to do something similar using Mockito and the when(functionIsCalled).thenReturn(mockValue) syntax.
You can use a function variable to set it to some other value for testing:
func defaultGetSecret(secretName string) (Credentials, error) {...}
var getSecret=defaultGetSecret
func main() {
...
credentials, err:=getSecret(...)
}
In your tests, you can change getSecret to point to something else:
func TestMain(t *testing.T) {
getSecret=func(secretName string) (Credentials,error) {
return mockCredentials,nil
}
main()
}
I have a set of functions, which uses the pool of objects. This pool has been mocked. It works fine in most of the cases. But in some functions i call the methods of objects from the pool. So i need to mock this objects too.
Lets say:
// ObjectGeter is a interface that is mocked
type ObjectGeter interface {
GetObject(id int) ObjectType, error
}
// this function is under test
func SomeFunc(og ObjectGeter,id int, otherArgument SomeType) error {
// some actions with otherArgument
// and may be return an error
obj, err := og.GetObject(id)
if err !=nil {
return errors.New("GetObject error")
}
rezult, err := obj.SomeMethod()
if err !=nil {
return errors.New("One of internal errors")
}
return rezult, nil
}
Is there a way to test whole this function? I can create interface SomeMethoder which wraps the SomeMethod(), but i can't find the way how to assign it to obj inside SomeFunc without changing the signature of GetObject to GetObject(id int) SomeMethoder,error.
Currently i see the one approach - testing by a parts.
The only solution i'v found without of changing of paradigm is a wrapper. It is pretty trivial but may be some one will need it once.
Originally i have some type:
type PoolType struct {...}
func (p *PoolType)GetObject(id int) (ObjectType, error) {...}
and interface, that wraps PoolType.GetObject and that i'v mocked.
Now i have the interface:
type SomeMethoder interface {
SomeMethod() (ResultType, error)
}
to wrap object returned by PoolType.GetObject().
To produce it i have interface:
type ObjectGeter interface {
GetObject(id int) (SomeMethoder, error)
}
and type
type MyObjectGeter struct {
pool *PoolType
}
func New(pool *PoolType) *MyObjectGeter {
return &MyObjectGeter{pool: pool}
}
func (p *MyObjectGeter)GetObject(id int) (SomeMethoder, error) {
return p.pool.GetObject(id)
}
that implements it.
So:
// this function is under test
func SomeFunc(og ObjectGeter,id int, otherArgument SomeType) error {
// some actions with otherArgument
// and may be return an error
iface, err := og.GetObject(id)
if err !=nil {
return errors.New("GetObject error")
}
rezult, err := iface.SomeMethod()
if err !=nil {
return errors.New("One of internal errors")
}
return rezult, nil
}
is called by
og := New(pool)
SomeFunc(og,id,otherArgument)
in real work.
After all to test whole SomeFunc i have to:
func TestSomeFuncSuccess (t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
objectGeter := mocks.NewMockObjectGeter(controller)
someMethoder := mocks.NewMockSomeMethoder(controller)
gomock.InOrder(
args.objectGeter.EXPECT().
GetObject(correctIdCOnst).
Return(someMethoder, nil),
args.someMethoder.EXPECT().
SomeMethod().
Return(NewResultType(...),nil).
Times(args.test.times[1]),
)
result, err := SomeFunc(objectGeter,correctIdCOnst,otherArgumentConst)
// some checks
}
So, the only untested part is MyObjectGeter.GetObject that is enough for me.