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

list

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

sopel.tests.factories.BotFactory

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

sopel.tests.factories.ConfigFactory

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 a Trigger

  • 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 channel

  • admin (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 if results are in regexp format

  • ignore (list) – strings to ignore

Returns

a test function for tested_func

Return type

function

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

sopel.tests.factories.IRCFactory

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.factories.TriggerFactory

sopel.tests.pytest_plugin.userfactory()

Fixture to get a user factory.

Returns

a factory to create mock users

Return type

sopel.tests.factories.UserFactory

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 purposes

  • preloads (list) – list of plugins to preload, setup, and register

Returns

a test instance of the bot

Return type

sopel.bot.Sopel

This will instantiate a Sopel object, replace its backend with a MockIRCBackend, and then preload plugins. This will automatically load the coretasks plugin, and every other plugin from preloads:

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 the clear_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

list

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 into message_sent.

is_connected()

Tell if the backend is connected or not.

Return type

bool

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 the rawlist() 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 to

  • join_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 the bot in response to messages sent via this MockIRCServer are finished running before execution can continue. If set to False, 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 the blocking arguments to the instance methods below.

The IRCFactory factory can be used to create such mock object, either directly or by using py.test and the ircfactory() 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
  • channel (str) – channel to send message for

  • users (list) – list (or tuple) of nicknames that will be present in the RPL_NAMREPLY event

  • blocking (bool) – whether to block until all triggered threads have finished (optional)

This will send 2 messages to the bot:

  • a RPL_NAMREPLY event (353), giving information about users present in channel

  • 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 the users parameter.

This is particularly useful to populate the bot’s memory of who is in a channel.

If blocking is True, this method will wait to join all running triggers’ threads before returning. Setting it to False will skip this step. If not specified, this MockIRCServer instance’s join_threads argument will be obeyed.

New in version 7.1: The blocking parameter.

See also

The join_threads argument to MockIRCServer.

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 from user.

Parameters
  • user (MockUser) – factory for the user who joins the channel

  • channel (str) – channel the user joined

  • blocking (bool) – whether to block until all triggered threads have finished (optional)

This will send a JOIN message as if user just joined the channel:

factory.join(MockUser('NewUser'), '#test')

If blocking is True, this method will wait to join all running triggers’ threads before returning. Setting it to False will skip this step. If not specified, this MockIRCServer instance’s join_threads argument will be obeyed.

New in version 7.1: The blocking parameter.

See also

The join_threads argument to MockIRCServer.

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
  • channel (str) – channel receiving the MODE event

  • flags (str) – MODE flags set

  • users (list) – users getting the MODE flags

  • blocking (bool) – whether to block until all triggered threads have finished (optional)

This will send a MODE message as if ChanServ added/removed channel modes for a set of users. This method assumes the flags parameter follows the IRC specification for MODE:

factory.mode_set('#test', '+vo-v', ['UserV', UserOP', 'UserAnon'])

If blocking is True, this method will wait to join all running triggers’ threads before returning. Setting it to False will skip this step. If not specified, this MockIRCServer instance’s join_threads argument will be obeyed.

New in version 7.1: The blocking parameter.

See also

The join_threads argument to MockIRCServer.

pm(user, text, blocking=None)

Send a PRIVMSG to the bot by a user.

Parameters
  • user (MockUser) – factory for the user object who sends a message

  • text (str) – content of the message sent to the bot

  • blocking (bool) – whether to block until all triggered threads have finished (optional)

This will send a PRIVMSG message as forwarded by the server for a user sending it to the bot:

factory.pm(MockUser('NewUser'), 'A private word.')

If blocking is True, this method will wait to join all running triggers’ threads before returning. Setting it to False will skip this step. If not specified, this MockIRCServer instance’s join_threads argument will be obeyed.

New in version 7.1: The blocking parameter.

See also

The join_threads argument to MockIRCServer.

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 to channel by user.

Parameters
  • user (MockUser) – factory for the user who sends a message to channel

  • channel (str) – recipient of the user’s PRIVMSG

  • text (str) – content of the message sent to the channel

  • blocking (bool) – whether to block until all triggered threads have finished (optional)

This will send a PRIVMSG message as if user sent it to the channel, and the server forwarded it to its clients:

factory.say(MockUser('NewUser'), '#test', '.shrug')

If blocking is True, this method will wait to join all running triggers’ threads before returning. Setting it to False will skip this step. If not specified, this MockIRCServer instance’s join_threads argument will be obeyed.

New in version 7.1: The blocking parameter.

See also

The join_threads argument to MockIRCServer.

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.

Parameters
  • nick (str) – nickname

  • user (str) – IRC username

  • host (str) – user’s host

The UserFactory factory can be used to create such mock object, either directly or by using py.test and the userfactory() fixture.

join(channel)

Generate a JOIN command forwarded by the server for the user.

Parameters

channel (str) – channel the user joined

Returns

the JOIN command the server sends to its clients present in the same channel when the user joins it.

Return type

str

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.

privmsg(recipient, text)

Generate a PRIVMSG command forwarded by a server for the user.

Parameters
  • recipient (str) – a channel name or the bot’s nick

  • text (str) – content of the message

Returns

a PRIVMSG command forwarded by the server as if it originated from the user’s hostmask

Return type

str

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 section

  • validate (bool) – whether to validate the section’s values (optional; defaults to True)

Raises

ValueError – if the section name has been defined already with a different cls_

If validate is True, the section’s values will be validated, and an exception (usually ValueError or AttributeError) raised if they are invalid. This is desirable in a plugin’s setup() function, for example, but might not be in the configure() 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:

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.