I have a problem firing a function declared as variable in golang with testify.
Test and function both declared in same package.
var testableFunction = func(abc string) string {...}
now i have a different file with unit test calling testableFunction
func TestFunction(t *testing.T){
...
res:=testableFunction("abc")
...
}
Calling TestFunction with go test does not fire any exception, but testableFunction is actually never run. Why?
That's because your testableFunction variable gets assigned somewhere else in your code.
See this example:
var testableFunction = func(s string) string {
return "re: " + s
}
Test code:
func TestFunction(t *testing.T) {
exp := "re: a"
if got := testableFunction("a"); got != exp {
t.Errorf("Expected: %q, got: %q", exp, got)
}
}
Running go test -cover:
PASS
coverage: 100.0% of statements
ok play 0.002s
Obviously if a new function value is assigned to testableFunction before the test execution, then the anonymous function used to initialize your variable will not get called by the test.
To demonstrate, change your test function to this:
func TestFunction(t *testing.T) {
testableFunction = func(s string) string { return "re: " + s }
exp := "re: a"
if got := testableFunction("a"); got != exp {
t.Errorf("Expected: %q, got: %q", exp, got)
}
}
Running go test -cover:
PASS
coverage: 0.0% of statements
ok play 0.003s
Related
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
UPDATED
I want to make helper function for testing reading env vars function. It uses envconfig.
func Test_T2(t *testing.T) {
os.Setenv("APP_PARAM_STR", "string value")
os.Setenv("APP_PARAM_INT", "12")
os.Setenv("APP_PARAM_DURATION", "15s")
os.Setenv("APP_PARAM_INT", "44")
c := ConfigTwo{}
d := ConfigTwo{
ParamDuration: 15*time.Second,
ParamInt: 44,
}
helper(t, &c, &d)
}
func helper(t *testing.T, confObject, expValue interface{}) {
t.Helper()
err := getParams(&confObject)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, expValue, confObject)
}
func getParams(cfg interface{}) error {
return envconfig.Process("APP", cfg)
}
** UPDATE 2 **
It works. Thanks everyone.
It works if I have getPrams function only. But if I add helper (that I need to test different structs) I get an error:
specification must be a struct pointer
envconfig performs two checks here:
Use this code. The argument is a pointer to the expected value.
func helper(t *testing.T, pexpected interface{}) {
t.Helper()
pactual := reflect.New(reflect.TypeOf(pexpected).Elem()).Interface()
err := getParams(pactual)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, pexpected, pactual)
}
The expression reflect.New(reflect.TypeOf(pexeceted).Elem()).Interface() returns a pointer to a new empty value with the same type as what pexpected points to.
Call it like this:
helper(t, &ConfigTwo{A: "expected A Field", B: "expected B field"}
I have the file util.go:
func Foo(service *SomeService) error {
return helper(service)
}
func helper(service *SomeService) error {
...
}
I'm writing unit tests using testify, starting with Foo. I want to:
mock helper
assert mocked helper was called 1 time
I saw some promising solutions at https://stackoverflow.com/a/19168875/1661745, but not sure about them:
Method 1: pass helper as parameter of Foo. My doubt: testify needs a Mock struct to AssertNumberOfCalls, and here there is no struct.
Method 2: create a struct for Foo. My doubt: I don't know if it makes sense to make a struct for utils. Also requires more refactoring since callers of Foo would need a utils struct.
What's the best way to do this?
If you just want to test the args being called in helper, this is an approach that I have been using. The same test will also prove that your helper was called exactly once.
// Code
var originalFn = func(arg1, arg2 string) {
...
}
func Foo() {
originalFn(arg1,arg2)
}
// Tests
func TestFoo(t *testing.T) {
tempFn := originalFn
var fnArgs []string
originalFn = func(arg1, arg2) {
fnArgs = append(fnArgs, []string{arg1, arg2})
}
defer originalFn = tempFn
tests := []struct{
expected []string
}{
{
expected: []string{"arg1", "arg2"},
},
}
for _, tt:= range tests {
fnArgs := make([]string, 0)
Foo()
assert.Equal(t, tt.expected, fnArgs)
}
}
My tests keep failing with but no actual calls happened but I am positive the func is getting called (It's a logging function so I see the logs on the terminal)
Basically I have code that looks something like this :
common/utils.go
func LogNilValue(ctx string){
log.Logger.Warn(ctx)
}
main.go
import (
"common/utils"
)
func CheckFunc(*string value) {
ctx := "Some context string"
if value == nil {
utils.LogNilValue(ctx) //void func that just logs the string
}
}
test.go
type MyMockedObject struct{
mock.Mock
}
func TestNil() {
m := new(MyMockedObject)
m.Mock.On("LogNilValue", mock.Anything).Return(nil)
CheckFunc(nil)
m.AssertCalled(s.T(), "LogNilValue", mock.Anything)
}
I expect this to work but then, I keep getting no actual calls happened. Not sure what I am doing wrong here.
LogNilValue should have MyMockedObject as the method receiver, in order to mock the method. Something like this
func (m *MyMockedObject)LogNilValue(ctx string) {
args := m.Called(ctx)
}
CheckFunc should look like this:
func CheckFunc(value *string, m *MyMockedObject) {
ctx := "Some context string"
if value == nil {
m.LogNilValue(ctx) //void func that just logs the string
}
}
And finally the TestNil method:
func TestNil() {
m := new(MyMockedObject)
m.Mock.On("LogNilValue", mock.Anything).Return(nil)
CheckFunc(nil, m)
m.AssertCalled(s.T(), "LogNilValue", mock.Anything)
}
Hi I am new to Go and I am writing a simple app which gets some configuration from the env variables. I do this in the init function as shown below.
type envVars struct {
Host string `env:"APP_HOST"`
Username string `env:"APP_USERNAME"`
Password string `env:"APP_PASSWORD"`
}
var envConfig envVars
func init() {
if err := env.Parse(&envConfig); err != nil {
log.Fatal(err)
}
}
I wrote test to verify of the env variables are being read correctly. But the problem is that my program's init func gets called even before my test's init func. Is there any way I can do some sort of setup before my program's init func gets called.
func init() {
os.Setenv("APP_HOST", "http://localhost:9999")
os.Setenv("APP_USERNAME", "john")
os.Setenv("APP_PASSWORD", "doe")
}
func TestEnvConfig(t *testing.T) {
assert.NotNil(t, envConfig)
assert.Equal(t, "http://localhost:9999", envConfig.Host)
}
You can use the TestMain func to control what happens before and after your tests.
For example:
func TestMain(m *testing.M) {
// Write code here to run before tests
// Run tests
exitVal := m.Run()
// Write code here to run after tests
// Exit with exit value from tests
os.Exit(exitVal)
}
func TestYourFunc(t *testing.T) {
// Test code
}
You can add a Test_parse_params(t *testing.T) function before your real tests. Look like this:
type envVars struct {
Host string `env:"APP_HOST"`
Username string `env:"APP_USERNAME"`
Password string `env:"APP_PASSWORD"`
}
var envConfig envVars
//parse command params
func Test_parse_params(t *testing.T) {
if err := env.Parse(&envConfig); err != nil {
log.Fatal(err)
}
}
func Test_real_test(t *testing.T) {
....
}
No, you shouldn't expect init() run in some order, (in fact it based on file loaded order, but still, you should not count on it).
The simple way is, if you want to test it, use a shell script to run you test, or something like Makefile.
Shell example:
set +e
export APP_HOST=http://localhost:9999
export APP_USERNAME=john
export APP_PASSWORD=doe
go test .
unset APP_HOST
unset APP_USERNAME
unset APP_PASSWORD
or a single line command:
APP_HOST=http://localhost:9999 APP_USERNAME=john APP_PASSWORD=doe go test .
Edit:
Other solution: move out the read env from init func.
func init(){
envInit()
}
func envInit(){
if err := env.Parse(&envConfig); err != nil {
log.Fatal(err)
}
}
Then you can call again envInit in your test to make sure it works.
Less than ideal, but this works for me.
Inside of the package that you're testing:
func init() {
if len(os.Args) > 1 && os.Args[1][:5] == "-test" {
log.Println("testing")//special test setup goes goes here
return // ...or just skip the setup entirely
}
//...
}