diff --git a/src/albumcoverfetcher.cpp b/src/albumcoverfetcher.cpp index 8fd120150..b486ef8b4 100644 --- a/src/albumcoverfetcher.cpp +++ b/src/albumcoverfetcher.cpp @@ -9,11 +9,15 @@ const int AlbumCoverFetcher::kMaxConcurrentRequests = 5; -AlbumCoverFetcher::AlbumCoverFetcher(QObject* parent) +AlbumCoverFetcher::AlbumCoverFetcher(QObject* parent, QNetworkAccessManager* network) : QObject(parent), + network_(network), next_id_(0), request_starter_(new QTimer(this)) { + if (!network_.get()) { + network_.reset(new QNetworkAccessManager); + } request_starter_->setInterval(1000); connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests())); } @@ -68,7 +72,7 @@ void AlbumCoverFetcher::AlbumGetInfoFinished() { lastfm::XmlQuery query(lastfm::ws::parse(reply)); QUrl image_url(query["album"]["image size=large"].text()); - QNetworkReply* image_reply = network_.get(QNetworkRequest(image_url)); + QNetworkReply* image_reply = network_->get(QNetworkRequest(image_url)); connect(image_reply, SIGNAL(finished()), SLOT(AlbumCoverFetchFinished())); active_requests_[image_reply] = id; diff --git a/src/albumcoverfetcher.h b/src/albumcoverfetcher.h index f27158890..424969b11 100644 --- a/src/albumcoverfetcher.h +++ b/src/albumcoverfetcher.h @@ -9,6 +9,8 @@ #include +#include + class QNetworkReply; class QString; @@ -16,7 +18,7 @@ class AlbumCoverFetcher : public QObject { Q_OBJECT public: - AlbumCoverFetcher(QObject* parent = 0); + AlbumCoverFetcher(QObject* parent = 0, QNetworkAccessManager* network_ = 0); virtual ~AlbumCoverFetcher() {} static const int kMaxConcurrentRequests; @@ -40,7 +42,7 @@ class AlbumCoverFetcher : public QObject { QString album; }; - QNetworkAccessManager network_; + boost::scoped_ptr network_; quint64 next_id_; QQueue queued_requests_; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 89a278b91..be5bec486 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,6 +29,17 @@ set(GMOCK-SOURCES add_library(gmock ${GMOCK-SOURCES}) target_link_libraries(gmock gtest) +set(MOCK-SOURCES + mock_networkaccessmanager.cpp) + +set(MOCK-MOC-HEADERS + mock_networkaccessmanager.h) + +qt4_wrap_cpp(MOCK-SOURCES-MOC ${MOCK-MOC-HEADERS}) + +add_library(mocks ${MOCK-SOURCES} ${MOCK-SOURCES-MOC}) +target_link_libraries(mocks gmock) + add_custom_target(test echo "Running tests" WORKING_DIRECTORY ${CURRENT_BINARY_DIR} @@ -41,7 +52,7 @@ macro(add_test_file test_source) ${ARGV0} main.cpp ) - target_link_libraries(${TEST_NAME} gmock clementine_lib) + target_link_libraries(${TEST_NAME} gmock clementine_lib mocks) add_custom_command(TARGET test POST_BUILD COMMAND ./${TEST_NAME}) add_dependencies(test ${TEST_NAME}) @@ -51,3 +62,4 @@ endmacro (add_test_file) add_test_file(m3uparser_test.cpp) add_test_file(song_test.cpp) add_test_file(librarybackend_test.cpp) +add_test_file(albumcoverfetcher_test.cpp) diff --git a/tests/albumcoverfetcher_test.cpp b/tests/albumcoverfetcher_test.cpp new file mode 100644 index 000000000..b3dd9b05d --- /dev/null +++ b/tests/albumcoverfetcher_test.cpp @@ -0,0 +1,59 @@ +#include "albumcoverfetcher.h" + +#include + +#include +#include +#include + +#include "mock_networkaccessmanager.h" +#include "gtest/gtest.h" + +int argc = 1; +char* argv[] = { "test", 0 }; + +class AlbumCoverFetcherTest : public ::testing::Test { + protected: + static void SetUpTestCase() { + lastfm::ws::ApiKey = "foobar"; + } + + AlbumCoverFetcherTest() + : app_(argc, (char**)argv) { + } + + void SetUp() { + network_ = new MockNetworkAccessManager; + lastfm::setNetworkAccessManager(network_); + } + + MockNetworkAccessManager* network_; + QCoreApplication app_; +}; + + +TEST_F(AlbumCoverFetcherTest, FetchesAlbumCover) { + const char* data = "BarFoo" + "http://example.com/image.jpg"; + + QMap params; + params["artist"] = "Foo"; + params["album"] = "Bar"; + params["api_key"] = "foobar"; + MockNetworkReply* get_info_reply = network_->ExpectGet("audioscrobbler", params, 200, data); + params.clear(); + MockNetworkReply* album_reply = network_->ExpectGet("http://example.com/image.jpg", params, 200, ""); + + AlbumCoverFetcher fetcher(NULL, network_); + QSignalSpy spy(&fetcher, SIGNAL(AlbumCoverFetched(quint64, const QImage&))); + ASSERT_TRUE(spy.isValid()); + fetcher.FetchAlbumCover("Foo", "Bar"); + + get_info_reply->Done(); + app_.processEvents(QEventLoop::AllEvents); + + album_reply->Done(); + app_.processEvents(QEventLoop::AllEvents); + + EXPECT_EQ(1, spy.count()); +} diff --git a/tests/mock_networkaccessmanager.cpp b/tests/mock_networkaccessmanager.cpp new file mode 100644 index 000000000..d7e91685e --- /dev/null +++ b/tests/mock_networkaccessmanager.cpp @@ -0,0 +1,110 @@ +#include "mock_networkaccessmanager.h" + +#include + +#include +using std::min; + +using ::testing::MakeMatcher; +using ::testing::Matcher; +using ::testing::MatcherInterface; +using ::testing::Return; + +class RequestForUrlMatcher : public MatcherInterface { + public: + RequestForUrlMatcher(const QString& contains, + const QMap& expected_params) + : contains_(contains), + expected_params_(expected_params) { + } + virtual ~RequestForUrlMatcher() {} + + virtual bool Matches(const QNetworkRequest& req) const { + const QUrl& url = req.url(); + + if (!url.toString().contains(contains_)) { + return false; + } + + for (QMap::const_iterator it = expected_params_.begin(); + it != expected_params_.end(); ++it) { + if (!url.hasQueryItem(it.key()) || + url.queryItemValue(it.key()) != it.value()) { + return false; + } + } + return true; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "matches url"; + } + + private: + QString contains_; + QMap expected_params_; +}; + +inline Matcher RequestForUrl( + const QString& contains, + const QMap& params) { + return MakeMatcher(new RequestForUrlMatcher(contains, params)); +} + +MockNetworkReply* MockNetworkAccessManager::ExpectGet( + const QString& contains, + const QMap& expected_params, + int status, + const char* data) { + MockNetworkReply* reply = new MockNetworkReply(data); + reply->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); + + EXPECT_CALL(*this, createRequest( + GetOperation, RequestForUrl(contains, expected_params), NULL)). + WillOnce(Return(reply)); + + return reply; +} + +MockNetworkAccessManager::~MockNetworkAccessManager() { +} + +MockNetworkReply::MockNetworkReply() + : data_(NULL), + size_(0) { +} + +MockNetworkReply::MockNetworkReply(const char* data) + : data_(data), + size_(strlen(data)), + pos_(0) { +} + +void MockNetworkReply::SetData(const char* data) { + data_ = data; + size_ = strlen(data); + pos_ = 0; +} + +qint64 MockNetworkReply::readData(char* data, qint64 size) { + if (size_ == pos_) { + return -1; + } + qint64 bytes_to_read = min(size_ - pos_, size); + memcpy(data, data_, bytes_to_read); + pos_ += bytes_to_read; + return bytes_to_read; +} + +qint64 MockNetworkReply::writeData(const char* data, qint64) { + ADD_FAILURE() << "Something tried to write to a QNetworkReply"; +} + +void MockNetworkReply::Done() { + setOpenMode(QIODevice::ReadOnly); + emit finished(); +} + +void MockNetworkReply::setAttribute(QNetworkRequest::Attribute code, const QVariant& value) { + QNetworkReply::setAttribute(code, value); +} diff --git a/tests/mock_networkaccessmanager.h b/tests/mock_networkaccessmanager.h new file mode 100644 index 000000000..d34b2f173 --- /dev/null +++ b/tests/mock_networkaccessmanager.h @@ -0,0 +1,49 @@ +#ifndef MOCK_NETWORKACCESSAMANGER_H +#define MOCK_NETWORKACCESSMANAGER_H + +#include +#include +#include +#include +#include + +#include "test_utils.h" +#include "gmock/gmock.h" + +class MockNetworkReply : public QNetworkReply { + Q_OBJECT + public: + MockNetworkReply(); + MockNetworkReply(const char* data); + virtual ~MockNetworkReply() {} + + void SetData(const char* data); + virtual void setAttribute(QNetworkRequest::Attribute code, const QVariant& value); + + void Done(); + + protected: + MOCK_METHOD0(abort, void()); + virtual qint64 readData(char* data, qint64); + virtual qint64 writeData(const char* data, qint64); + + const char* data_; + qint64 size_; + qint64 pos_; +}; + + +class MockNetworkAccessManager : public QNetworkAccessManager { + Q_OBJECT + public: + virtual ~MockNetworkAccessManager(); + MockNetworkReply* ExpectGet( + const QString& contains, + const QMap& params, + int status, + const char* ret_data); + protected: + MOCK_METHOD3(createRequest, QNetworkReply*(Operation, const QNetworkRequest&, QIODevice*)); +}; + +#endif diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp index c8d3eaaab..09fcacdab 100644 --- a/tests/test_utils.cpp +++ b/tests/test_utils.cpp @@ -1,5 +1,6 @@ #include "test_utils.h" +#include #include #include @@ -13,3 +14,7 @@ std::ostream& operator <<(std::ostream& stream, const QUrl& url) { return stream; } +std::ostream& operator <<(std::ostream& stream, const QNetworkRequest& req) { + stream << req.url().toString().toStdString(); + return stream; +} diff --git a/tests/test_utils.h b/tests/test_utils.h index fe4d69e78..f066593e8 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -3,10 +3,12 @@ #include +class QNetworkRequest; class QString; class QUrl; std::ostream& operator <<(std::ostream& stream, const QString& str); std::ostream& operator <<(std::ostream& stream, const QUrl& url); +std::ostream& operator <<(std::ostream& stream, const QNetworkRequest& req); #endif // TEST_UTILS_H