From 00b2be0943a4133a9758637ee4d8c50df6c30dc6 Mon Sep 17 00:00:00 2001 From: M Ikram Ullah Khan <44160462+IkramKhanNiazi@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:43:57 +0500 Subject: [PATCH] Add unit tests for the StatsMailer extension (#7013) --- scrapy/extensions/statsmailer.py | 2 +- tests/test_extension_statsmailer.py | 82 +++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/test_extension_statsmailer.py diff --git a/scrapy/extensions/statsmailer.py b/scrapy/extensions/statsmailer.py index 221628642..9e900308d 100644 --- a/scrapy/extensions/statsmailer.py +++ b/scrapy/extensions/statsmailer.py @@ -40,7 +40,7 @@ class StatsMailer: return o def spider_closed(self, spider: Spider) -> Deferred[None] | None: - spider_stats = self.stats.get_stats(spider) + spider_stats = self.stats.get_stats() body = "Global stats\n\n" body += "\n".join(f"{k:<50} : {v}" for k, v in self.stats.get_stats().items()) body += f"\n\n{spider.name} stats\n\n" diff --git a/tests/test_extension_statsmailer.py b/tests/test_extension_statsmailer.py new file mode 100644 index 000000000..b6322325c --- /dev/null +++ b/tests/test_extension_statsmailer.py @@ -0,0 +1,82 @@ +from unittest.mock import MagicMock + +import pytest + +from scrapy import signals +from scrapy.exceptions import NotConfigured +from scrapy.extensions import statsmailer +from scrapy.mail import MailSender +from scrapy.signalmanager import SignalManager +from scrapy.statscollectors import StatsCollector +from scrapy.utils.spider import DefaultSpider + + +@pytest.fixture +def dummy_stats(): + class DummyStats(StatsCollector): + def __init__(self): + # pylint: disable=super-init-not-called + self._stats = {"global_item_scraped_count": 42} + + def get_stats(self): + return {"item_scraped_count": 10, **self._stats} + + return DummyStats() + + +def test_from_crawler_without_recipients_raises_notconfigured(): + crawler = MagicMock() + crawler.settings.getlist.return_value = [] + crawler.stats = MagicMock() + + with pytest.raises(NotConfigured): + statsmailer.StatsMailer.from_crawler(crawler) + + +def test_from_crawler_with_recipients_initializes_extension(dummy_stats, monkeypatch): + crawler = MagicMock() + crawler.settings.getlist.return_value = ["test@example.com"] + crawler.stats = dummy_stats + crawler.signals = SignalManager(crawler) + + mailer = MagicMock(spec=MailSender) + monkeypatch.setattr(statsmailer.MailSender, "from_crawler", lambda _: mailer) + + ext = statsmailer.StatsMailer.from_crawler(crawler) + + assert isinstance(ext, statsmailer.StatsMailer) + assert ext.recipients == ["test@example.com"] + assert ext.mail is mailer + + +def test_from_crawler_connects_spider_closed_signal(dummy_stats, monkeypatch): + crawler = MagicMock() + crawler.settings.getlist.return_value = ["test@example.com"] + crawler.stats = dummy_stats + crawler.signals = SignalManager(crawler) + + mailer = MagicMock(spec=MailSender) + monkeypatch.setattr(statsmailer.MailSender, "from_crawler", lambda _: mailer) + + statsmailer.StatsMailer.from_crawler(crawler) + + connected = crawler.signals.send_catch_log( + signals.spider_closed, spider=DefaultSpider(name="dummy") + ) + assert connected is not None + + +def test_spider_closed_sends_email(dummy_stats): + recipients = ["test@example.com"] + mail = MagicMock(spec=MailSender) + ext = statsmailer.StatsMailer(dummy_stats, recipients, mail) + + spider = DefaultSpider(name="dummy") + ext.spider_closed(spider) + + args, kwargs = mail.send.call_args + to, subject, body = args + assert to == recipients + assert "Scrapy stats for: dummy" in subject + assert "global_item_scraped_count" in body + assert "item_scraped_count" in body