-
Notifications
You must be signed in to change notification settings - Fork 20
Unit Testing Best Known Methods
- Mocking Jenkins Dependencies
- Mocking Environment Variables
- Mock external shared library methods
- Mock errors
- Mock external shared library methods
- References
Always leverage the builtin capabilities of the Jenkins-Spock framework for mocking Jenkins plugins. For example, if you come across the following error when unit testing your code:
java.lang.IllegalStateException: During a test, the pipeline step [stepName] was called but there was no mock for it.
The error above denotes that the code under test calls a pipeline step stepName
but there is no mock for it. You are able to explicitly mock the pipeline step using explictlyMockPipelineStep
method available in the Jenkins-Spock framework. However it is recommended that the plugin that contains the corresponding step be added as a dependency in the build.gradle
file. For instructions on how to do this, refer to the Add plugin dependency to Gradle section.
- Note the name of the Pipeline Step to add.
- Go to Pipeline Steps Reference page.
- Use your browser and search for the Pipeline Step within the page.
- If the Pipeline Step is found, click on the Pipeline that it belongs to, the page for the respective Pipeline should open.
- Under the heading click on the
View this plugin on the Plugins site
link, the plugins.jenkins.io page should open. - In the plugins.jenkins.io page note the ID for the Pipeline. You will use this ID in the next step.
- Go to Maven Repository page.
- Enter the ID in the search, and locate the result from the results displayed, click on the respective link.
- In the page, click on the
Jenkins Releases
tab. - If you know the version then click it, otherwise click on the latest version that is listed.
- In the Gradle tab, note the group, name and version.
- Edit the
build.gradle
file, add the dependency found above to the dependencies section.
Always ensure the source code under test uses one of the following idioms for getting or setting Environment Variables, doing this will simplify the ability to mock environment variables in the unit test:
- Getting the value of an environment variable
env.VARIABLE
env[VARIABLE]
"${env.VARIABLE}"
- Setting the value of an environment variable
env.VARIABLE = VALUE
env[VARIABLE] = VALUE
Within your unit tests, environment variables are set using the .getBinding().setVariable('name', 'value')
idiom. Where the name is env
and the value is a map you define within your unit test. The map should define all environment variables the code under test expects, likewise the map can be used to assert any environment variables that the code under test sets.
A good example of this practice is the EdgeXSetupEnvironmentSpec
The edgex-global-pipelines
Jenkins shared library consists of multiple scripts exposing methods for various functional areas, where each script is named after the particular functional area it serves. The shared library includes a EdgeX
script that serves as utility script containing methods that are shared amongst other scripts. It is common practice for a method in one script call a method in another script, to mock the interaction you use the explictlyMockPipelineVariable
to mock the script, then getPipelineMock
method to verify the interaction or stub it if necessary.
Mock the external script named script
:
explictlyMockPipelineVariable('script')
It is recommended to mock all external scripts called within the script under test in the Test Spec setup.
Get the script mock and stub the call to method
to return 'value'
for any argument passed in:
getPipelineMock('script.method').call(_) >> 'value'
Integration Testing is defined as a type of testing where software modules are integrated logically and tested as a group. The Jenkins-Spock framework provides the ability to load any number of scripts to test within a given Spec Test. There are instances where performing integration tests is more practical, if you wish to do so then we recommend naming the Spec Test with Int
as to differentiate between unit and integration tests.
A good example of this practice is the EdgeXReleaseDockerImageIntSpec
Always leverage error
when wanting to conditionally abort part of your script. Error is a Pipeline Step whose plugin has been added as a dependency to our project thus is already mocked by the framework. An example showing how you can assert that an error is thrown with a specific message:
1 * getPipelineMock('error').call('error message')
The difficulties of mocking functions within the same script under test have been described in the following issue: Issue 78. Due to the nature of how the scripts that comprise the edgex-global-pipelines
shared library are written; where a deliberate intent is made to develop small, functionally cohesive methods that contribute to a single well-defined task. This development intent results in having scripts with multi-layered call graphs, where methods may call multiple methods from within the same script. We find that the workaround provided in the issue is complicated and doesn't scale well in our environment. For these reasons the method outlined below is being suggested.
- For the script under test, document its call graph. A call graph is a control flow graph, which represents calling relationships between methods in a script or program. Each node represents a method and each edge (f, g) indicates that method f calls method g. An example EdgeXReleaseGitTag call graph is depicted below.
- Create a second script with the same name as the original script with the word Util added to the end, for example
EdgeXReleaseGitTagUtil.groovy
. - Analyze the call graph, methods that reside in odd numbered layers should continue to reside in the first script, methods at even numbered layers should be moved from the first script into the second script.
- Create a Spec Test for both scripts.
Mocking of methods between both scripts follow the same pattern described for Mock external shared library methods. The only difference with this approach is that the scripts are (for the lack of a better word) name spaced for the respective functional area.
NOTE The approach outlined above is not recommended as the standard development approach, but as an alternative to re-writing the script under test if mocking of the internal method calls becomes unwieldy.