Hi!
I have enabled RSpec verifying doubles in all remaining YaST unit tests where they
were not used. The advantage is that RSpec checks that the mocked methods really
exist in the original class. Without it if you make a typo in a method call in the
code and make the same typo in the unit test mock then the test will pass but the
code will actually crash at run time.
So ideally we should enable this feature everywhere.
But in the YaST unit test we quite often replace some YaST modules with an empty
class to avoid too long or cyclic build dependencies. If you mock an YaST module
completely then you have the same problem back as the dummy methods are really
defined there so even RSpec verifying doubles will not catch the problems there.
To use advantages from the both worlds I have created a new
Yast::RSpec::Helpers.define_yast_module helper method. [1]
It tries to load the requested YaST module via the usual YaST.import call. If that
fails then it defines a dummy replacement for it. So if the module is present in the
system, like when you run the unit tests locally on your machine or when running in
GitHub Actions where most of the modules are present, then it will load the real
modules and the verifying doubles will fully work.
In RPM build it will create dummy modules and the build dependencies can be
avoided.
Examples:
# defined here, needs yast2-ruby-bindings >= 4.4.7
require "yast/rspec"
# if you do not mock the module methods in the tests you can use
# this simple variant
Yast::RSpec::Helpers.define_yast_module("AutoInstall")
# if you mock some methods then they must be defined in the mocked class
# so the verifying doubles check does not fail
Yast::RSpec::Helpers.define_yast_module("Profile", methods: [:current])
# note: for simplification all mocked methods accept any number of parameters,
# the real parameters should be verified when running against the real modules
# you can also pass a block which is evaluated in the context of the created
class
# so you can define some defaults, in theory you could define also some logic
there
# but I'd avoid it, the mocked logic might easily go out of sync with the real
module
Yast::RSpec::Helpers.define_yast_module("Language") do
def language
# the default
"en_US"
end
end
Then if you run "rake test:unit" locally the modules will be loaded from system when
found. If you want to force creating the mocked modules even when they are present in
the system then just set the YAST_FORCE_MODULE_STUBS environment variable, e.g.:
YAST_FORCE_MODULE_STUBS=1 rake test:unit
This is useful if you want to test locally how it will work in RPM build, whether
your mocks implement all the methods used in the tests.
You can check some real examples in [2] and [3].
And interestingly enabling verifying doubles found a real bug in the code! [4]
Enjoy!
Ladislav
[1] https://github.com/yast/yast-ruby-bindings/pull/281
[2] https://github.com/yast/yast-bootloader/pull/663/files
[3] https://github.com/yast/yast-network/pull/1278/files
[4]
https://github.com/yast/yast-installation/pull/1013/files#diff-05183469b1a050c36780c8d60d2c4647a1d8e16189a4d1535fbca825a1770e8f
--
Ladislav Slezák
YaST Developer
SUSE LINUX, s.r.o.
Corso IIa
Křižíkova 148/34
18600 Praha 8