README ¶
components
Golang code testing and generation package.
Setup
Generation
Install:
go install github.com/flywingedai/components@latest
Usage:
components $PATH
Testing
Package Installation:
go get github.com/flywingedai/components/tests
Examples
See the examples folder to see example usage of the generate directives and unit tests written using the components/tests package.
Generate
The components package provides generation directives that help you:
- Create interfaces for your components
- Create mock files using the mockery package.
- Generate standardized component tests quickly
Struct File
// TODO: Struct file overview
Registration
type component struct {
/*
generate::components
interfaceName::$STRING_VALUE
interfaceFolder::$STRING_VALUE
interfaceFile::$STRING_VALUE
mockFolder::$STRING_VALUE
mockFile::$STRING_VALUE
skipTestFile::$BOOL_VALUE
blackbox::$BOOL_VALUE
expecters::$$STRING_VALUE
config::$STRING_VALUE
*/
field1 string
field2 int
subComponent interfaces.SubComponent `pkg:"$mockPackage" new:"$newMock" type:"$mockType"`
}
To define options for component generation, just add them in the above format somewhere in your struct in a comment.
In order for the components command to register a struct as something that
should be generated, you need to add the generate::components
string somewhere
withing that struct's body. The above shows the standard way of doing it, but as
long as that string appears somewhere, it will be recognized.
The components command recognizes each of the above option before the "::" as valid. The commanf will look for all instances of "::" in the struct body, and extract the option key and the option value. If any non-recognized options are found, the command will fail. Below is a brief description of all the options:
- generate: Requires value to be "components". Registers the struct as a
component that should be generated by the
components
command. - interfaceName: [Optional] Name of the generated interface. If ignored, will set the generated interface name to the name of the component with a capital letter to export it.
- interfaceFolder: [Optional] The folder (and package) the interface will live in. It is assumed the package name is the base of the provided directory path. If ignored, will create the interface in the same folder and package the struct is defined in.
- interfaceFile: [Optional] The base name of the file to place the generated
interface into. Will default to
$interfaceName.go
if nothing is provided AND the interface folder is different than the package folder. If the interface folder is the package folder (which is whatinterfaceFolder
defaults to), theinterfaceFile
will be set to the file the struct was defined in. (Will append the interface to the end of the file along with the generated "New" function.) - mockFolder: [Optional] The folder (and package) the mocks generated by
the mockery command will live in. It is assumed the package name is the base of
the provided directory path. Defaults to
interfaceFolder/{{interfaceFolderBase}}_mocks
. If you pass in a value of__package__
, it will default topackageFolder/{{packageFolderBase}}_mocks
instead. - mockFile: [Optional] The name of the generated mock file for this struct.
Defaults to
{{interfaceName}}.go
with interfaceName having a lowercase first letter. - skipTestFile: [Optional] Whether or not to generate a test file. This test
file will have mock definitions to use for creating tests for this specific
component. Defaults to
false
. Set to true byskipTestFile::true
. If true, blackbox and expecters options don't have any effect as those are options specific to the test file. - blackbox: [Optional] Whether or not the generated test file will be placed
in the same package as the struct or not. If enabled, this facilitates
"blackbox" testing where the test files are all part of a new
{{package}}_test
package which does not have access to private values within the package. To enable,blackbox::true
- expecters: [Optional] Whether or not the generate test file will have expecter bindings automatically generated for the given mock fields. Each mock that should be included should be separated by a ",". To ignore all values, set expecters = "-".
- config: [Optional] The mockery config file to use for this component
generation. The path should be relative to the place you execute the components
command or be absolute. Some options do not work because the components package
needs them set a specific way.
with-expecter
will always be true, andfilename
is automatically inherited based on themockFile
option.
Params
// TODO
Params.Convert()
// TODO
Test File
If skipTestFile
is not set to true a test file will be created for your
component. Below are all the parts of the generated test file.
Struct Interface
// TODO
mocks
// TODO: Describe what this struct actually is
To specify that a field of the component is a mock and should be treated as
such, you will need to set the pkg
, new
, and type
tags.
- pkg: The name of the mock package
- new: The name of the function which creates a new mocked version of this type
- type: The name of the type as referred to by the mocks
The above tags can be automatically inferred by using the pkg:"-"
tag. This
will tell the components command that all the values are standard. The command
will automatically generated standard mocks, so if you are mocking something
that was made with the components command, this will work. Standard values are
below:
- pkg: The name of the existing package + "_mocks"
- new: "New" + existing type
- type: The same as the defined type
example:
type component struct {
field1 string
subComponent subcomponent.SubComponent `pkg:"-"`
}
is converted to
type mocks struct {
field1 string
subComponent *subcomponent_mocks.SubComponent
}
initParams()
// TODO
convert()
// TODO
buildMocks()
// TODO
mock_*()
// TODO
Test
The test package is built upon the idea of three structs. tests.TestOptions
,
tests.TestConfig
, and tests.TestState
.
TestOptions: tests.TestOptions
are the backbone of this package. They are
created from a defined tester by calling tester.NewOptions()
. Additionally,
the tester has an Options
attribute which gets automatically applied to every
test that is registered with that tester. This is useful if you have some
generic setup that needs to happen.
There are many different methods on the tests.TestOptions
struct. Those will
be explained in a later section.
TestConfig: The test config is a meta object that stores all the options
related to the test, as well as the parent function that will be called during
the test. Test configs are created via the following tests.TestOptions
methods:
- CreateTest()
- CreateMethodTest()
- CreateFunctionTest()
TestState: When each test is run, there is a state object that is passed
around. Many of the tests.TestOptions
methods can take advantage of this for
more advanced use cases. The object looks like this:
type TestState[C, M, D any] struct {
Assertions *assert.Assertions
Component C
Mocks *M
Data *D
Input []interface{}
Output []interface{}
}
Component
andMocks
attributes are what is returned bytester.buildMocksFunction()
Data
is what is returned by thetester.initDataFunction()
Input
is what is fed into the function/method that is being testedOutput
is what is returned by the tested function/methodAssertions
is a assert object you may use to do explicit testing if your particular test requires it.
A new test state is created with completely new Component
, Mocks
, and Data
attributes at the start of each registered test.
Tester
The tester is the main object that manages tests. Create one with one of the below new tester functions. There are a couple important arguments and attributes of the tester.
buildMocksFunction: This is a required argument in some of the new tester
functions. This function is automatically created by the generate command if you
did not enable the skipTestFile
option. You are able to create this function
yourself if you'd like, but it is recommended to use the generated command in
tandem to keep everything up to date.
initDataFunction: This is a required argument in some of the new tester
functions. This defines the Data
attribute of each TestState
New Tester functions:
NewTesterWithData(buildMocksFunction, initDataFunction)
NewTesterWithInit(buildMocksFunction, initDataFunction)
NewTesterWithoutInit(buildMocksFunction)
NewFunctionTester(initDataFunction)
NewFunctionTesterWithoutData()
Tester Methods
NewOptions()
- Create a new set of options affiliated with this testerRegisterTests(...tests)
- Register tests with the testerTest()
- Run all tests registered with this tester
TestOptions
Test
TestConfig
Basic Usage
tester := tests.NewFunctionTester()
input1, input2 := 0, 0
tester.Options = tester.Options.
SetInputs_P(&input1, &input2)
expectedError := errors.New("expected error")
tester.NewOptions().
Prepare(func() {input1 = 3}).
Outputs(0, expectedError).
RegisterTest("failure test", functionToTest)
tester.NewOptions().
Prepare(func() {
input1 = 42
input2 = 42
}).
Outputs(42, nil).
RegisterTest("success", functionToTest)
tester.Test(t)
Documentation ¶
There is no documentation for this package.