Testing tools¶
Common tools¶
Test tools, factories, pytest fixtures, and mocks.
New in version 7.0.
-
sopel.tests.
rawlist
(*args)¶ Build a list of raw IRC messages from the lines given as
*args
.- Returns
a list of raw IRC messages as seen by the bot
- Return type
This is a helper function to build a list of messages without having to care about encoding or this pesky carriage return:
>>> rawlist('PRIVMSG :Hello!') [b'PRIVMSG :Hello!\r\n']
Fixtures with py.test¶
Pytest plugin for Sopel.
New in version 7.0.
-
sopel.tests.pytest_plugin.
botfactory
()¶ Fixture to get a Bot factory.
- Returns
a factory to create a mocked bot instance
- Return type
This is very useful in unit tests:
def test_bot(configfactory, botfactory): settings = configfactory('... skip for clarity ...') bot = botfactory(settings) # no plugins loaded # ... do something with the bot def test_bot_loaded(configfactory, botfactory): settings = configfactory('... skip for clarity ...') bot = botfactory.preloaded(settings, ['myplugin']) # now the bot has `coretasks` and `myplugin` loaded
-
sopel.tests.pytest_plugin.
configfactory
(tmpdir)¶ Fixture to get a config factory.
- Returns
a factory to create test settings
- Return type
The factory will be automatically configured with a
tmpdir
object.
-
sopel.tests.pytest_plugin.
get_disable_setup
()¶ Generate a pytest fixture to setup the plugin before running its tests.
When using
@example
for a plugin callable with an expected output, pytest will be used to run it as a test. In order to work, this fixture must be added to the plugin to set up the plugin before running the test.
-
sopel.tests.pytest_plugin.
get_example_test
(tested_func, msg, results, privmsg, admin, owner, repeat, use_regexp, ignore=[])¶ Get a function that calls
tested_func
with fake wrapper and trigger.- Parameters
tested_func (callable) – a Sopel callable that accepts a
SopelWrapper
and aTrigger
msg (str) – message that is supposed to trigger the command
results (list) – expected output from the callable
privmsg (bool) – if
True
, make the message appear to have arrived in a private message to the bot; otherwise make it appear to have come from a channeladmin (bool) – make the message appear to have come from an admin
owner (bool) – make the message appear to have come from an owner
repeat (int) – how many times to repeat the test; useful for tests that return random stuff
use_regexp (bool) – pass
True
ifresults
are in regexp formatignore (list) – strings to ignore
- Returns
a test function for
tested_func
- Return type
-
sopel.tests.pytest_plugin.
insert_into_module
(func, module_name, base_name, prefix)¶ Add a function into a module.
This can be used to add a test function, a setup function, or a fixture to an existing module to be used with pytest.
-
sopel.tests.pytest_plugin.
ircfactory
()¶ Fixture to get an IRC factory.
- Returns
a factory to create mock IRC servers
- Return type
For example, a plugin command could be tested with this:
from sopel.tests import rawlist def test_mycommand(configfactory, botfactory, ircfactory, userfactory): settings = configfactory('... skip for clarity ...') bot = botfactory(settings, ['myplugin']) irc = ircfactory(bot) user = userfactory('User') irc.say(user, '#test', '.mycommand') assert bot.backend.message_sent == rawlist( 'PRIVMSG #test :My plugin replied this.' )
-
sopel.tests.pytest_plugin.
triggerfactory
()¶ Fixture to get a trigger factory.
- Returns
a factory to create triggers
- Return type
-
sopel.tests.pytest_plugin.
userfactory
()¶ Fixture to get a user factory.
- Returns
a factory to create mock users
- Return type
def test_mycommand(userfactory): user = userfactory('User') assert user.nick == 'User' assert user.user == 'user' assert user.host == 'example.com' assert user.prefix == 'User!user@example.com'
Factories¶
Test factories: they create objects for testing purposes.
New in version 7.0.
-
class
sopel.tests.factories.
BotFactory
¶ Factory to create bot.
See also
The
botfactory()
fixture can be used to instantiate this factory.-
preloaded
(settings, preloads=None)¶ Create a bot and preload its plugins.
- Parameters
settings (
sopel.config.Config
) – Sopel’s configuration for testing purposespreloads (list) – list of plugins to preload, setup, and register
- Returns
a test instance of the bot
- Return type
This will instantiate a
Sopel
object, replace its backend with aMockIRCBackend
, and then preload plugins. This will automatically load thecoretasks
plugin, and every other plugin frompreloads
:factory = BotFactory() bot = factory.preloaded(settings, ['emoticons', 'remind'])
Note
This will automatically setup plugins: be careful with plugins that require access to external services on setup.
You may also need to manually call shutdown routines for the loaded plugins.
-
-
class
sopel.tests.factories.
ConfigFactory
(tmpdir)¶ Factory to create settings.
See also
The
configfactory()
fixture can be used to instantiate this factory.
-
class
sopel.tests.factories.
IRCFactory
¶ Factory to create mock IRC server.
See also
The
ircfactory()
fixture can be used to create this factory.
-
class
sopel.tests.factories.
TriggerFactory
¶ Factory to create trigger.
See also
The
triggerfactory()
fixture can be used to instantiate this factory.
-
class
sopel.tests.factories.
UserFactory
¶ Factory to create mock user.
See also
The
userfactory()
fixture can be used to create this factory.
Mocks¶
Test mocks: they fake objects for testing.
New in version 7.0.
-
class
sopel.tests.mocks.
MockIRCBackend
(*args, **kwargs)¶ Fake IRC connection backend for testing purpose.
- Parameters
bot (
sopel.bot.Sopel
) – a Sopel instance
This backend doesn’t require an actual connection. Instead, it stores every message sent in the
message_sent
list.You can use the
rawlist()
function to compare the messages easily, and theclear_message_sent()
method to clear previous messages:>>> from sopel.tests import rawlist, mocks >>> backend = mocks.MockIRCBackend(bot=None) >>> backend.irc_send(b'PRIVMSG #channel :Hi!\r\n') >>> backend.message_sent == rawlist('PRIVMSG #channel :Hi!') True >>> backend.clear_message_sent() [b'PRIVMSG #channel :Hi!\r\n'] >>> backend.message_sent []
See also
The
parent class
contains all the methods that can be used on this test backend.-
clear_message_sent
()¶ Clear and return previous messages sent.
- Returns
a copy of the cleared messages sent
- Return type
New in version 7.1.
-
connected
¶ Convenient status flag.
Set to
True
to make the bot think it is connected.
-
irc_send
(data)¶ Store
data
intomessage_sent
.
-
message_sent
¶ List of raw messages sent by the bot.
This list will be populated each time the
irc_send()
method is used: it will contain the raw IRC lines the bot wanted to send.You can clear this list with the
clear_message_sent()
method, or use therawlist()
function to compare it.
-
class
sopel.tests.mocks.
MockIRCServer
(bot, join_threads=True)¶ Fake IRC Server that can send messages to a test bot.
- Parameters
bot (
sopel.bot.Sopel
) – test bot instance to send messages tojoin_threads (bool) – whether message functions should join running threads before returning (default:
True
)
This mock object helps developers when they want to simulate an IRC server sending messages to the bot.
The default
join_threads
behavior is suitable for testing most common plugin callables, and ensures that all callables dispatched by thebot
in response to messages sent via thisMockIRCServer
are finished running before execution can continue. If set toFalse
, the mock server will not wait for the bot to finish processing threaded callables before returning.Note
You can override
join_threads
on a per-method-call basis with theblocking
arguments to the instance methods below.The
IRCFactory
factory can be used to create such mock object, either directly or by usingpy.test
and theircfactory()
fixture.New in version 7.1: The
join_threads
parameter.-
channel_joined
(channel, users=None, blocking=None)¶ Send events as if the bot just joined a channel.
- Parameters
This will send 2 messages to the bot:
a
RPL_NAMREPLY
event (353), giving information aboutusers
present inchannel
a
RPL_ENDOFNAMES
event (366) for completion
Use this to emulate when the bot joins a channel, and the server replies with the list of connected users:
factory.channel_joined('#test', ['Owner', '@ChanServ'])
In this example, the bot will know that there are 2 other users present in
#test
: “Owner” (a regular user) and “ChanServ” (which is a channel operator). Note that the bot itself will be added to the list of users automatically, and you should not pass it in theusers
parameter.This is particularly useful to populate the bot’s memory of who is in a channel.
If
blocking
isTrue
, this method will wait to join all running triggers’ threads before returning. Setting it toFalse
will skip this step. If not specified, thisMockIRCServer
instance’sjoin_threads
argument will be obeyed.New in version 7.1: The
blocking
parameter.See also
The
join_threads
argument toMockIRCServer
.Note
To add a user to a channel after using this method, you should use the
join()
method.
-
property
chanserv
¶ ChanServ’s message prefix.
-
join
(user, channel, blocking=None)¶ Send a
channel
JOIN event fromuser
.- Parameters
This will send a
JOIN
message as ifuser
just joined the channel:factory.join(MockUser('NewUser'), '#test')
If
blocking
isTrue
, this method will wait to join all running triggers’ threads before returning. Setting it toFalse
will skip this step. If not specified, thisMockIRCServer
instance’sjoin_threads
argument will be obeyed.New in version 7.1: The
blocking
parameter.See also
The
join_threads
argument toMockIRCServer
.See also
This function is a shortcut to call the bot with the result from the user factory’s
join()
method.
-
mode_set
(channel, flags, users, blocking=None)¶ Send a MODE event for a
channel
- Parameters
This will send a MODE message as if
ChanServ
added/removed channel modes for a set ofusers
. This method assumes theflags
parameter follows the IRC specification for MODE:factory.mode_set('#test', '+vo-v', ['UserV', UserOP', 'UserAnon'])
If
blocking
isTrue
, this method will wait to join all running triggers’ threads before returning. Setting it toFalse
will skip this step. If not specified, thisMockIRCServer
instance’sjoin_threads
argument will be obeyed.New in version 7.1: The
blocking
parameter.See also
The
join_threads
argument toMockIRCServer
.
-
pm
(user, text, blocking=None)¶ Send a
PRIVMSG
to the bot by auser
.- Parameters
This will send a
PRIVMSG
message as forwarded by the server for auser
sending it to the bot:factory.pm(MockUser('NewUser'), 'A private word.')
If
blocking
isTrue
, this method will wait to join all running triggers’ threads before returning. Setting it toFalse
will skip this step. If not specified, thisMockIRCServer
instance’sjoin_threads
argument will be obeyed.New in version 7.1: The
blocking
parameter.See also
The
join_threads
argument toMockIRCServer
.See also
This function is a shortcut to call the bot with the result from the user factory’s
privmsg()
method, using the bot’s nick as recipient.
-
say
(user, channel, text, blocking=None)¶ Send a
PRIVMSG
tochannel
byuser
.- Parameters
This will send a
PRIVMSG
message as ifuser
sent it to thechannel
, and the server forwarded it to its clients:factory.say(MockUser('NewUser'), '#test', '.shrug')
If
blocking
isTrue
, this method will wait to join all running triggers’ threads before returning. Setting it toFalse
will skip this step. If not specified, thisMockIRCServer
instance’sjoin_threads
argument will be obeyed.New in version 7.1: The
blocking
parameter.See also
The
join_threads
argument toMockIRCServer
.See also
This function is a shortcut to call the bot with the result from the user’s
privmsg()
method.
-
class
sopel.tests.mocks.
MockUser
(nick=None, user=None, host=None)¶ Fake user that can generate messages to send to a bot.
The
UserFactory
factory can be used to create such mock object, either directly or by usingpy.test
and theuserfactory()
fixture.-
join
(channel)¶ Generate a
JOIN
command forwarded by the server for the user.
-
property
prefix
¶ User’s hostmask as seen by other users on the server.
When the server forwards a User’s command, it uses this prefix.
-
Old testing tools¶
This module provided tools that helped to write tests.
Deprecated since version 7.1: This module will be removed in Sopel 8.
It formerly contained mock classes for the bot, its wrapper, and its config object. As the module is deprecated, so are they, and they will be removed as well.
New code should use the pytest plugin
for Sopel; or should take advantage of the mocks
and
factories
modules, both added in Sopel 7.0.
-
class
sopel.test_tools.
MockConfig
¶ -
define_section
(name, cls_)¶ Define the available settings in a section.
- Parameters
name (str) – name of the new section
cls_ (subclass of
StaticSection
) – class defining the settings within the sectionvalidate (bool) – whether to validate the section’s values (optional; defaults to
True
)
- Raises
ValueError – if the section
name
has been defined already with a differentcls_
If
validate
isTrue
, the section’s values will be validated, and an exception (usuallyValueError
orAttributeError
) raised if they are invalid. This is desirable in a plugin’ssetup()
function, for example, but might not be in theconfigure()
function.Important
The section’s
name
SHOULD follow snake_case naming rules:use only lowercase letters, digits, and underscore (
_
)SHOULD NOT start with a digit
Deviations from snake_case can break the following operations:
accessing the section from Python code using the
Config
object’s attributesoverriding the section’s values using environment variables
-
-
class
sopel.test_tools.
MockSopelWrapper
(*args, **kwargs)¶
-
sopel.test_tools.
get_disable_setup
()¶ Get a function to prevent conflict between pytest and plugin’s setup.
Deprecated since version 7.1: This is now part of the Sopel pytest plugin at
sopel.tests.pytest_plugin
.
-
sopel.test_tools.
get_example_test
(*args, **kwargs)¶ Get a function that calls
tested_func
with fake wrapper and trigger.Deprecated since version 7.1: This is now part of the Sopel pytest plugin at
sopel.tests.pytest_plugin
.
-
sopel.test_tools.
insert_into_module
(*args, **kwargs)¶ Add a function into a module.
Deprecated since version 7.1: This is now part of the Sopel pytest plugin at
sopel.tests.pytest_plugin
.