diff --git a/QtOAuth.pri b/QtOAuth.pri
index f889a6306724a78a916c9c9f41dfed0ee4f378b5..7b3c0e9e81daf90f9585909b2745d05326c9acb1 100644
--- a/QtOAuth.pri
+++ b/QtOAuth.pri
@@ -3,4 +3,4 @@ DEPENDPATH += $$OUT_PWD/../QtOAuth
 
 win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../QtOAuth/release/ -lQtOAuth
 else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../QtOAuth/debug/ -lQtOAuth
-else:unix: LIBS += -L$$OUT_PWD/../QtOAuth/ -lQtOAuth
+else:unix: LIBS += -L$$OUT_PWD/../../QtOAuth/ -lQtOAuth
diff --git a/QtOAuth.pro b/QtOAuth.pro
index 33aef473b08f1ed0c02a46a797f3c3b2933e64a3..e2c552e418441909c35fc0bf8c9dcb2207ebee0b 100644
--- a/QtOAuth.pro
+++ b/QtOAuth.pro
@@ -1,3 +1,6 @@
 TEMPLATE = subdirs
 
-SUBDIRS += QtOAuth twittertimeline redditclient
+SUBDIRS += \
+    QtOAuth \
+    tests \
+    examples
diff --git a/QtOAuth/private/qoauth1_p.h b/QtOAuth/private/qoauth1_p.h
index fd95cb4f6aeaaf7d00fdca097653c3bcc2ad4157..9e75806376c57f6ae501635235741a0465c9d3f6 100644
--- a/QtOAuth/private/qoauth1_p.h
+++ b/QtOAuth/private/qoauth1_p.h
@@ -87,8 +87,11 @@ public:
                          QNetworkAccessManager::Operation operation,
                          const QVariantMap parameters);
 
-    QNetworkReply *requestToken(QNetworkAccessManager::Operation operation, const QUrl &url,
+    QNetworkReply *requestToken(QNetworkAccessManager::Operation operation,
+                                const QUrl &url,
+                                const QOAuthTokenPair &token,
                                 const QVariantMap &additionalParameters);
+
     QNetworkReply *requestAuthorizationGrant(QNetworkAccessManager::Operation operation,
                                              const QUrl &url,
                                              const QVariantMap &additionalParameters);
diff --git a/QtOAuth/qoauth1.cpp b/QtOAuth/qoauth1.cpp
index 6e382c891f4fae43f4df0ac153056a9fb85c9aac..b7d87516a9c22b637ef3646d07644b703e06a7b2 100644
--- a/QtOAuth/qoauth1.cpp
+++ b/QtOAuth/qoauth1.cpp
@@ -92,8 +92,6 @@ void QOAuth1Private::_q_onTokenRequestFinished()
     else {
         q->setToken(qMakePair(token, tokenSecret));
         q->setStatus(QAbstractOAuth::Status::TokenReceived);
-
-        q->postAuthorizationGrant();
     }
 }
 
@@ -187,22 +185,14 @@ void QOAuth1Private::appendSignature(QVariantMap *headers, const QUrl &url,
 }
 
 QNetworkReply *QOAuth1Private::requestToken(QNetworkAccessManager::Operation operation,
-                                            const QUrl &url, const QVariantMap &parameters)
+                                            const QUrl &url,
+                                            const QOAuthTokenPair &token,
+                                            const QVariantMap &parameters)
 {
     Q_Q(QOAuth1);
-    if (q->status() != QAbstractOAuth::Status::NotAuthenticated) {
-        qWarning("Already authenticated");
-        return nullptr;
-    } else if (!networkAccessManager) {
-        qCritical("QNetworkAccessManager not available");
-        return nullptr;
-    }
-
-    q->setStatus(QAbstractOAuth::Status::NotAuthenticated);
-    q->setToken(QOAuthTokenPair());
-
-    extraTokens.clear();
-    Q_EMIT q->extraTokensChanged(extraTokens);
+    Q_ASSERT(networkAccessManager);
+    Q_ASSERT(operation == QNetworkAccessManager::GetOperation ||
+             operation == QNetworkAccessManager::PostOperation);
 
     QNetworkRequest request(url);
     request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
@@ -210,6 +200,8 @@ QNetworkReply *QOAuth1Private::requestToken(QNetworkAccessManager::Operation ope
     QVariantMap headers;
     appendCommonHeaders(&headers);
     headers.insert(QStringLiteral("oauth_callback"), q->callback());
+    if (!token.first.isEmpty())
+        headers.insert(QStringLiteral("oauth_token"), token.first);
     appendSignature(&headers, url, operation, parameters);
 
     request.setRawHeader("Authorization", q->generateAuthorizationHeader(headers));
@@ -382,6 +374,49 @@ QNetworkReply *QOAuth1::post(const QNetworkRequest &req, const QVariantMap &para
     return d->networkAccessManager->post(request, data.toLatin1());
 }
 
+QNetworkReply *QOAuth1::requestToken(QNetworkAccessManager::Operation operation,
+                                     const QUrl &url,
+                                     const QVariantMap &additionalParameters)
+{
+    Q_D(QOAuth1);
+    if (Q_UNLIKELY(!d->networkAccessManager)) {
+        qCritical("QNetworkAccessManager not available");
+        return nullptr;
+    }
+    if (Q_UNLIKELY(url.isEmpty())) {
+        qCritical() << "Request Url not set";
+        return nullptr;
+    }
+    if (Q_UNLIKELY(operation != QNetworkAccessManager::GetOperation &&
+                   operation != QNetworkAccessManager::PostOperation)) {
+        qCritical("Operation not supported");
+        return nullptr;
+    }
+    return d->requestToken(operation, url, d->token, additionalParameters);
+}
+
+QNetworkReply *QOAuth1::requestToken(QNetworkAccessManager::Operation operation,
+                                     const QUrl &url,
+                                     const QOAuthTokenPair &token,
+                                     const QVariantMap &additionalParameters)
+{
+    Q_D(QOAuth1);
+    if (!d->networkAccessManager) {
+        qCritical("QNetworkAccessManager not available");
+        return nullptr;
+    }
+    if (Q_UNLIKELY(url.isEmpty())) {
+        qCritical() << "Request Url not set";
+        return nullptr;
+    }
+    if (Q_UNLIKELY(operation != QNetworkAccessManager::GetOperation &&
+                   operation != QNetworkAccessManager::PostOperation)) {
+        qCritical("Operation not supported");
+        return nullptr;
+    }
+    return d->requestToken(operation, url, token, additionalParameters);
+}
+
 void QOAuth1::setup(QNetworkRequest *request, const QVariantMap &signingParameters,
                     QNetworkAccessManager::Operation operation)
 {
@@ -551,10 +586,13 @@ void QOAuth1::grant()
 
         if (d->status == Status::NotAuthenticated) {
             d->setStatus(Status::TokenReceived);
-            postAuthorizationGrant();
+            requestToken(QNetworkAccessManager::PostOperation, authorizationGrantUrl());
+        } else if (d->status == Status::TokenReceived) {
+            d->setStatus(Status::Authenticated);
+            Q_EMIT granted();
         }
     });
-    postRequestToken(extraTokens());
+    requestToken(QNetworkAccessManager::PostOperation, requestTokenUrl(), QOAuthTokenPair());
 }
 
 void QOAuth1::setExtraTokens(const QVariantMap &tokens)
@@ -563,56 +601,6 @@ void QOAuth1::setExtraTokens(const QVariantMap &tokens)
     d->extraTokens = tokens;
 }
 
-QNetworkReply * QOAuth1::getRequestToken(const QVariantMap &parameters)
-{
-    Q_D(QOAuth1);
-    if (!d->networkAccessManager) {
-        qCritical("QNetworkAccessManager not available");
-        return nullptr;
-    }
-    if (Q_UNLIKELY(requestTokenUrl().isEmpty())) {
-        qCritical() << "Request Url not set";
-        return nullptr;
-    }
-    return d->requestToken(QNetworkAccessManager::GetOperation, requestTokenUrl(),
-                           parameters);
-}
-
-QNetworkReply * QOAuth1::postRequestToken(const QVariantMap &parameters)
-{
-    Q_D(QOAuth1);
-    if (Q_UNLIKELY(d->requestTokenUrl.isEmpty())) {
-        qCritical("Request Url not set");
-        return nullptr;
-    }
-    return d->requestToken(QNetworkAccessManager::PostOperation, d->requestTokenUrl,
-                           parameters);
-}
-
-QNetworkReply * QOAuth1::getAuthorizationGrant(const QVariantMap &parameters)
-{
-    Q_D(QOAuth1);
-    if (Q_UNLIKELY(d->authorizationGrantUrl.isEmpty())) {
-        qCritical("Authorization grant Url not set");
-        return nullptr;
-    }
-    return d->requestAuthorizationGrant(QNetworkAccessManager::GetOperation,
-                                        d->authorizationGrantUrl,
-                                        parameters);
-}
-
-QNetworkReply * QOAuth1::postAuthorizationGrant(const QVariantMap &parameters)
-{
-    Q_D(QOAuth1);
-    if (Q_UNLIKELY(d->authorizationGrantUrl.isEmpty())) {
-        qCritical("Authorization grant Url not set");
-        return nullptr;
-    }
-    return d->requestAuthorizationGrant(QNetworkAccessManager::PostOperation,
-                                        d->authorizationGrantUrl,
-                                        parameters);
-}
-
 QT_END_NAMESPACE
 
 #endif // QT_NO_HTTP
diff --git a/QtOAuth/qoauth1.h b/QtOAuth/qoauth1.h
index 3f3250abb8c72b8c5bf04c2c5dad0d5f54648478..c8c5038385a4851409eb75b992165620e38318bd 100644
--- a/QtOAuth/qoauth1.h
+++ b/QtOAuth/qoauth1.h
@@ -50,7 +50,9 @@ class QOAuth1Private;
 class Q_NETWORK_EXPORT QOAuth1: public QAbstractOAuth
 {
     Q_OBJECT
-    Q_PROPERTY(SignatureMethod signatureMethod READ signatureMethod WRITE setSignatureMethod
+    Q_PROPERTY(SignatureMethod signatureMethod
+               READ signatureMethod
+               WRITE setSignatureMethod
                NOTIFY signatureMethodChanged)
 
 public:
@@ -68,6 +70,15 @@ public:
     virtual QNetworkReply *post(const QNetworkRequest &req,
                                 const QVariantMap &parameters = QVariantMap()) override;
 
+    QNetworkReply *requestToken(QNetworkAccessManager::Operation operation,
+                                const QUrl &url,
+                                const QVariantMap &additionalParameters = QVariantMap());
+
+    QNetworkReply *requestToken(QNetworkAccessManager::Operation operation,
+                                const QUrl &url,
+                                const QOAuthTokenPair &token,
+                                const QVariantMap &additionalParameters = QVariantMap());
+
     virtual void grant() override;
 
 Q_SIGNALS:
@@ -76,12 +87,6 @@ Q_SIGNALS:
 protected:
     void setExtraTokens(const QVariantMap &tokens);
 
-    virtual QNetworkReply *getRequestToken(const QVariantMap &parameters = QVariantMap());
-    virtual QNetworkReply *postRequestToken(const QVariantMap &parameters = QVariantMap());
-
-    virtual QNetworkReply *getAuthorizationGrant(const QVariantMap &parameters = QVariantMap());
-    virtual QNetworkReply *postAuthorizationGrant(const QVariantMap &parameters = QVariantMap());
-
     void setup(QNetworkRequest *request,
                const QVariantMap &signingParameters,
                QNetworkAccessManager::Operation operation);
diff --git a/examples/examples.pro b/examples/examples.pro
new file mode 100644
index 0000000000000000000000000000000000000000..e00590dcb50ddefe7f3d07db9463ef9d0780b68c
--- /dev/null
+++ b/examples/examples.pro
@@ -0,0 +1,5 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+    twittertimeline \
+    redditclient
diff --git a/redditclient/main.cpp b/examples/redditclient/main.cpp
similarity index 100%
rename from redditclient/main.cpp
rename to examples/redditclient/main.cpp
diff --git a/redditclient/redditclient.pro b/examples/redditclient/redditclient.pro
similarity index 87%
rename from redditclient/redditclient.pro
rename to examples/redditclient/redditclient.pro
index 8ccf801174062fd1b4460579c3a7b90e39df2878..ba69b60293eaa5dfa6750809038045a535981f07 100644
--- a/redditclient/redditclient.pro
+++ b/examples/redditclient/redditclient.pro
@@ -13,4 +13,4 @@ HEADERS += \
 
 FORMS +=
 
-include(../QtOAuth.pri)
+include(../../QtOAuth.pri)
diff --git a/redditclient/redditmodel.cpp b/examples/redditclient/redditmodel.cpp
similarity index 100%
rename from redditclient/redditmodel.cpp
rename to examples/redditclient/redditmodel.cpp
diff --git a/redditclient/redditmodel.h b/examples/redditclient/redditmodel.h
similarity index 100%
rename from redditclient/redditmodel.h
rename to examples/redditclient/redditmodel.h
diff --git a/redditclient/redditwrapper.cpp b/examples/redditclient/redditwrapper.cpp
similarity index 100%
rename from redditclient/redditwrapper.cpp
rename to examples/redditclient/redditwrapper.cpp
diff --git a/redditclient/redditwrapper.h b/examples/redditclient/redditwrapper.h
similarity index 100%
rename from redditclient/redditwrapper.h
rename to examples/redditclient/redditwrapper.h
diff --git a/redditclient/uic_wrapper.sh b/examples/redditclient/uic_wrapper.sh
similarity index 100%
rename from redditclient/uic_wrapper.sh
rename to examples/redditclient/uic_wrapper.sh
diff --git a/twittertimeline/main.cpp b/examples/twittertimeline/main.cpp
similarity index 100%
rename from twittertimeline/main.cpp
rename to examples/twittertimeline/main.cpp
diff --git a/twittertimeline/twitter.cpp b/examples/twittertimeline/twitter.cpp
similarity index 100%
rename from twittertimeline/twitter.cpp
rename to examples/twittertimeline/twitter.cpp
diff --git a/twittertimeline/twitter.h b/examples/twittertimeline/twitter.h
similarity index 100%
rename from twittertimeline/twitter.h
rename to examples/twittertimeline/twitter.h
diff --git a/twittertimeline/twitterdialog.ui b/examples/twittertimeline/twitterdialog.ui
similarity index 100%
rename from twittertimeline/twitterdialog.ui
rename to examples/twittertimeline/twitterdialog.ui
diff --git a/twittertimeline/twittertimeline.pro b/examples/twittertimeline/twittertimeline.pro
similarity index 92%
rename from twittertimeline/twittertimeline.pro
rename to examples/twittertimeline/twittertimeline.pro
index dfa76042358e10c871bea689045c11970a8f290d..3929f9262c1fafbe12e89c93e1991f1699d19b06 100644
--- a/twittertimeline/twittertimeline.pro
+++ b/examples/twittertimeline/twittertimeline.pro
@@ -18,5 +18,5 @@ INSTALLS += target
 FORMS += \
     twitterdialog.ui
 
-include(../QtOAuth.pri)
+include(../../QtOAuth.pri)
 
diff --git a/twittertimeline/twittertimelinemodel.cpp b/examples/twittertimeline/twittertimelinemodel.cpp
similarity index 100%
rename from twittertimeline/twittertimelinemodel.cpp
rename to examples/twittertimeline/twittertimelinemodel.cpp
diff --git a/twittertimeline/twittertimelinemodel.h b/examples/twittertimeline/twittertimelinemodel.h
similarity index 100%
rename from twittertimeline/twittertimelinemodel.h
rename to examples/twittertimeline/twittertimelinemodel.h
diff --git a/tests/qoauth/qoauth.pro b/tests/qoauth/qoauth.pro
new file mode 100644
index 0000000000000000000000000000000000000000..3cf5b39f3093ab662f1dedc2bb492b9470f18605
--- /dev/null
+++ b/tests/qoauth/qoauth.pro
@@ -0,0 +1,9 @@
+CONFIG += testcase
+TARGET = tst_qoauth
+SOURCES  += tst_qoauth.cpp
+
+INCLUDEPATH += /
+
+QT = core core-private network testlib
+
+include(../../QtOAuth.pri)
diff --git a/tests/qoauth/tst_qoauth.cpp b/tests/qoauth/tst_qoauth.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ad64e7fa3725928818fc3a5680b7d7eb4b9a7223
--- /dev/null
+++ b/tests/qoauth/tst_qoauth.cpp
@@ -0,0 +1,518 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore>
+#include <QtTest>
+#include <QtNetwork>
+
+#include <qoauth1.h>
+#include <qabstractoauth.h>
+#include <private/qoauth1_p.h>
+#include <private/qabstractoauth_p.h>
+
+Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
+Q_DECLARE_METATYPE(QAbstractOAuth::Error)
+
+typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr;
+
+class tst_QOAuth: public QObject
+{
+    Q_OBJECT
+
+    QEventLoop *loop;
+    enum RunSimpleRequestReturn { Timeout = 0, Success, Failure };
+    int returnCode;
+
+    using QObject::connect;
+    static bool connect(const QNetworkReplyPtr &ptr, const char *signal, const QObject *receiver, const char *slot, Qt::ConnectionType ct = Qt::AutoConnection)
+    { return connect(ptr.data(), signal, receiver, slot, ct); }
+    bool connect(const QNetworkReplyPtr &ptr, const char *signal, const char *slot, Qt::ConnectionType ct = Qt::AutoConnection)
+    { return connect(ptr.data(), signal, slot, ct); }
+
+public:
+    int waitForFinish(QNetworkReplyPtr &reply);
+    void test_data();
+    void fillParameters(QVariantMap *parameters, const QUrlQuery &query);
+
+
+public Q_SLOTS:
+    void finished();
+    void gotError();
+
+private Q_SLOTS:
+    void createSignature();
+
+    void getToken_data();
+    void getToken();
+
+    void grant_data();
+    void grant();
+
+    void authenticatedCalls_data();
+    void authenticatedCalls();
+};
+
+int tst_QOAuth::waitForFinish(QNetworkReplyPtr &reply)
+{
+    int count = 0;
+
+    connect(reply, SIGNAL(finished()), SLOT(finished()));
+    connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(gotError()));
+    returnCode = Success;
+    loop = new QEventLoop;
+    QSignalSpy spy(reply.data(), SIGNAL(downloadProgress(qint64,qint64)));
+    while (!reply->isFinished()) {
+        QTimer::singleShot(5000, loop, SLOT(quit()));
+        if ( loop->exec() == Timeout && count == spy.count() && !reply->isFinished()) {
+            returnCode = Timeout;
+            break;
+        }
+        count = spy.count();
+    }
+    delete loop;
+    loop = 0;
+
+    return returnCode;
+}
+
+void tst_QOAuth::test_data()
+{
+    QTest::addColumn<QString>("consumerKey");
+    QTest::addColumn<QString>("consumerSecret");
+    QTest::addColumn<QString>("requestToken");
+    QTest::addColumn<QString>("requestTokenSecret");
+    QTest::addColumn<QString>("accessToken");
+    QTest::addColumn<QString>("accessTokenSecret");
+    QTest::addColumn<QUrl>("requestTokenUrl");
+    QTest::addColumn<QUrl>("accessTokenUrl");
+    QTest::addColumn<QUrl>("authenticatedCallUrl");
+    QTest::addColumn<QNetworkAccessManager::Operation>("requestType");
+
+    QTest::newRow("term.ie_get") << "key"
+                                 << "secret"
+                                 << "requestkey"
+                                 << "requestsecret"
+                                 << "accesskey"
+                                 << "accesssecret"
+                                 << QUrl("http://term.ie/oauth/example/request_token.php")
+                                 << QUrl("http://term.ie/oauth/example/access_token.php")
+                                 << QUrl("http://term.ie/oauth/example/echo_api.php")
+                                 << QNetworkAccessManager::GetOperation;
+    QTest::newRow("term.ie_post") << "key"
+                                  << "secret"
+                                  << "requestkey"
+                                  << "requestsecret"
+                                  << "accesskey"
+                                  << "accesssecret"
+                                  << QUrl("http://term.ie/oauth/example/request_token.php")
+                                  << QUrl("http://term.ie/oauth/example/access_token.php")
+                                  << QUrl("http://term.ie/oauth/example/echo_api.php")
+                                  << QNetworkAccessManager::PostOperation;
+    QTest::newRow("oauthbin.com_get") << "key"
+                                      << "secret"
+                                      << "requestkey"
+                                      << "requestsecret"
+                                      << "accesskey"
+                                      << "accesssecret"
+                                      << QUrl("http://oauthbin.com/v1/request-token")
+                                      << QUrl("http://oauthbin.com/v1/access-token")
+                                      << QUrl("http://oauthbin.com/v1/echo")
+                                      << QNetworkAccessManager::GetOperation;
+    QTest::newRow("oauthbin.com_post") << "key"
+                                       << "secret"
+                                       << "requestkey"
+                                       << "requestsecret"
+                                       << "accesskey"
+                                       << "accesssecret"
+                                       << QUrl("http://oauthbin.com/v1/request-token")
+                                       << QUrl("http://oauthbin.com/v1/access-token")
+                                       << QUrl("http://oauthbin.com/v1/echo")
+                                       << QNetworkAccessManager::PostOperation;
+}
+
+void tst_QOAuth::fillParameters(QVariantMap *parameters, const QUrlQuery &query)
+{
+    const auto list = query.queryItems();
+    for (auto it = list.begin(), end = list.end(); it != end; ++it)
+        parameters->insert(it->first, it->second);
+}
+
+void tst_QOAuth::finished()
+{
+    loop->exit(returnCode = Success);
+}
+
+void tst_QOAuth::gotError()
+{
+    loop->exit(returnCode = Failure);
+    disconnect(QObject::sender(), SIGNAL(finished()), this, 0);
+}
+
+void tst_QOAuth::createSignature()
+{
+    // Example from https://dev.twitter.com/oauth/overview/creating-signatures
+
+    QByteArray parameterString, signatureBase, signature;
+
+    const QUrl url(QStringLiteral(
+                       "https://api.twitter.com/1/statuses/update.json?include_entities=true"));
+    const QString data = QUrl::fromPercentEncoding("status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21");
+    const auto consumerSecret = QStringLiteral("kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw");
+    const auto oauthTokenSecret = QStringLiteral("LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE");
+
+    QNetworkAccessManager::Operation operation = QNetworkAccessManager::PostOperation;
+    QVariantMap oauthParams;
+
+    oauthParams.insert(QStringLiteral("oauth_consumer_key"),
+                       QStringLiteral("xvz1evFS4wEEPTGEFPHBog"));
+    oauthParams.insert(QStringLiteral("oauth_nonce"),
+                       QStringLiteral("kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg"));
+    oauthParams.insert(QStringLiteral("oauth_signature_method"),
+                       QStringLiteral("HMAC-SHA1"));
+    oauthParams.insert(QStringLiteral("oauth_timestamp"), QStringLiteral("1318622958"));
+    oauthParams.insert(QStringLiteral("oauth_token"),
+                       QStringLiteral("370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb"));
+    oauthParams.insert(QStringLiteral("oauth_version"), QStringLiteral("1.0"));
+
+    QVariantMap otherParametersFromData;
+    fillParameters(&otherParametersFromData, QUrlQuery(data));
+
+    // HTTP Method
+    QCOMPARE(QOAuth1Private::operationName(operation), QStringLiteral("POST"));
+    // Base URL
+    QCOMPARE(url.toString(QUrl::RemoveQuery),
+             QStringLiteral("https://api.twitter.com/1/statuses/update.json"));
+
+    struct OAuthSubClass : QOAuth1 {
+        using QOAuth1::parameterString;
+        using QOAuth1::signatureBase;
+        using QOAuth1::sign;
+    };
+
+    // Creating parameter string
+    {
+        QVariantMap parameters = oauthParams;
+        parameters.unite(otherParametersFromData);
+        fillParameters(&parameters, QUrlQuery(url.query()));
+        parameterString = OAuthSubClass::parameterString(parameters);
+
+        parameters.insertMulti(parameters.keys().first(), QString());
+        QTest::ignoreMessage(QtWarningMsg, QRegularExpression("^QOAuth: duplicated key .*$"));
+        OAuthSubClass::parameterString(parameters);
+    }
+
+    QCOMPARE(parameterString, QByteArray("include_entities=true"
+                                         "&oauth_consumer_key=xvz1evFS4wEEPTGEFPHBog"
+                                         "&oauth_nonce=kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg"
+                                         "&oauth_signature_method=HMAC-SHA1"
+                                         "&oauth_timestamp=1318622958"
+                                         "&oauth_token=370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb"
+                                         "&oauth_version=1.0"
+                                         "&status=Hello%20Ladies%20%2B%20Gentlemen%2C%20a%20signed%20OAuth%20request%21"));
+
+    // Collecting parameters
+    oauthParams.unite(otherParametersFromData);
+    signatureBase = OAuthSubClass::signatureBase(oauthParams, url, operation);
+    QCOMPARE(signatureBase, QByteArray("POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json"
+                                       "&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog"
+                                       "%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg"
+                                       "%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958"
+                                       "%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb"
+                                       "%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521"));
+
+    // Getting a signing key
+    signature = OAuthSubClass::sign(oauthParams, url, operation, consumerSecret, oauthTokenSecret);
+    QCOMPARE(signature, QByteArray("tnnArxj06cWHq44gCs1OSKk/jLY="));
+}
+
+void tst_QOAuth::getToken_data()
+{
+    QTest::addColumn<QOAuthTokenPair>("key");
+    QTest::addColumn<QOAuthTokenPair>("token");
+    QTest::addColumn<QOAuthTokenPair>("expectedToken");
+    QTest::addColumn<QUrl>("url");
+    QTest::addColumn<QNetworkAccessManager::Operation>("requestType");
+
+    // term.ie
+
+    QTest::newRow("term.ie_request_get") << qMakePair(QStringLiteral("key"),
+                                                      QStringLiteral("secret"))
+                                         << qMakePair(QStringLiteral(),
+                                                      QStringLiteral())
+                                         << qMakePair(QStringLiteral("requestkey"),
+                                                      QStringLiteral("requestsecret"))
+                                         << QUrl("http://term.ie/oauth/example/request_token.php")
+                                         << QNetworkAccessManager::GetOperation;
+
+    QTest::newRow("term.ie_request_post") << qMakePair(QStringLiteral("key"),
+                                                       QStringLiteral("secret"))
+                                          << qMakePair(QStringLiteral(),
+                                                       QStringLiteral())
+                                          << qMakePair(QStringLiteral("requestkey"),
+                                                       QStringLiteral("requestsecret"))
+                                          << QUrl("http://term.ie/oauth/example/request_token.php")
+                                          << QNetworkAccessManager::PostOperation;
+
+    QTest::newRow("term.ie_access_get") << qMakePair(QStringLiteral("key"),
+                                                     QStringLiteral("secret"))
+                                        << qMakePair(QStringLiteral("requestkey"),
+                                                     QStringLiteral("requestsecret"))
+                                        << qMakePair(QStringLiteral("accesskey"),
+                                                     QStringLiteral("accesssecret"))
+                                        << QUrl("http://term.ie/oauth/example/access_token.php")
+                                        << QNetworkAccessManager::GetOperation;
+
+    QTest::newRow("term.ie_access_post") << qMakePair(QStringLiteral("key"),
+                                                      QStringLiteral("secret"))
+                                         << qMakePair(QStringLiteral("requestkey"),
+                                                      QStringLiteral("requestsecret"))
+                                         << qMakePair(QStringLiteral("accesskey"),
+                                                      QStringLiteral("accesssecret"))
+                                         << QUrl("http://term.ie/oauth/example/access_token.php")
+                                         << QNetworkAccessManager::PostOperation;
+
+    // oauthbin.com
+
+    QTest::newRow("oauthbin.com_request_get") << qMakePair(QStringLiteral("key"),
+                                                           QStringLiteral("secret"))
+                                              << qMakePair(QStringLiteral(),
+                                                           QStringLiteral())
+                                              << qMakePair(QStringLiteral("requestkey"),
+                                                           QStringLiteral("requestsecret"))
+                                              << QUrl("http://oauthbin.com/v1/request-token")
+                                              << QNetworkAccessManager::GetOperation;
+
+    QTest::newRow("oauthbin.com_request_post") << qMakePair(QStringLiteral("key"),
+                                                            QStringLiteral("secret"))
+                                               << qMakePair(QStringLiteral(),
+                                                            QStringLiteral())
+                                               << qMakePair(QStringLiteral("requestkey"),
+                                                            QStringLiteral("requestsecret"))
+                                               << QUrl("http://oauthbin.com/v1/request-token")
+                                               << QNetworkAccessManager::PostOperation;
+
+    QTest::newRow("oauthbin.com_access_get") << qMakePair(QStringLiteral("key"),
+                                                          QStringLiteral("secret"))
+                                             << qMakePair(QStringLiteral("requestkey"),
+                                                          QStringLiteral("requestsecret"))
+                                             << qMakePair(QStringLiteral("accesskey"),
+                                                          QStringLiteral("accesssecret"))
+                                             << QUrl("http://oauthbin.com/v1/access-token")
+                                             << QNetworkAccessManager::GetOperation;
+
+    QTest::newRow("oauthbin.com_access_post") << qMakePair(QStringLiteral("key"),
+                                                           QStringLiteral("secret"))
+                                              << qMakePair(QStringLiteral("requestkey"),
+                                                           QStringLiteral("requestsecret"))
+                                              << qMakePair(QStringLiteral("accesskey"),
+                                                           QStringLiteral("accesssecret"))
+                                              << QUrl("http://oauthbin.com/v1/access-token")
+                                              << QNetworkAccessManager::PostOperation;
+}
+
+void tst_QOAuth::getToken()
+{
+    QFETCH(QOAuthTokenPair, key);
+    QFETCH(QOAuthTokenPair, token);
+    QFETCH(QOAuthTokenPair, expectedToken);
+    QFETCH(QUrl, url);
+    QFETCH(QNetworkAccessManager::Operation, requestType);
+
+    QOAuthTokenPair tokenReceived;
+    QNetworkAccessManager networkAccessManager;
+    QNetworkReplyPtr reply;
+
+    QOAuth1 o1(&networkAccessManager);
+
+    o1.setClientId(key);
+    o1.setToken(token);
+    o1.setRequestTokenUrl(url);
+    reply.reset(o1.requestToken(requestType, url));
+    QVERIFY(!reply.isNull());
+    connect(&o1, &QAbstractOAuth::tokenChanged, [&tokenReceived](
+            const QOAuthTokenPair &token){
+        tokenReceived = token;
+    });
+    QVERIFY(waitForFinish(reply) == Success);
+    QVERIFY(!tokenReceived.first.isEmpty() && !tokenReceived.second.isEmpty());
+}
+
+void tst_QOAuth::grant_data()
+{
+    test_data();
+}
+
+void tst_QOAuth::grant()
+{
+    QFETCH(QString, consumerKey);
+    QFETCH(QString, consumerSecret);
+    QFETCH(QString, requestToken);
+    QFETCH(QString, requestTokenSecret);
+    QFETCH(QString, accessToken);
+    QFETCH(QString, accessTokenSecret);
+    QFETCH(QUrl, requestTokenUrl);
+    QFETCH(QUrl, accessTokenUrl);
+
+    bool tokenReceived = false;
+    QNetworkAccessManager networkAccessManager;
+
+    QOAuth1 o1(&networkAccessManager);
+
+    {
+        QSignalSpy spy(&o1, &QAbstractOAuth::requestTokenUrlChanged);
+        o1.setRequestTokenUrl(requestTokenUrl);
+        QCOMPARE(spy.count(), 1);
+    }
+    {
+        QSignalSpy spy(&o1, &QAbstractOAuth::clientIdChanged);
+        o1.setClientId(qMakePair(consumerKey, consumerSecret));
+        QCOMPARE(spy.count(), 1);
+    }
+    {
+        QSignalSpy spy(&o1, &QAbstractOAuth::authorizationGrantUrlChanged);
+        o1.setAuthorizationGrantUrl(accessTokenUrl);
+        QCOMPARE(spy.count(), 1);
+    }
+    connect(&o1, &QAbstractOAuth::statusChanged, [&](QAbstractOAuth::Status status) {
+        if (status == QAbstractOAuth::Status::TokenReceived) {
+            if (!requestToken.isEmpty())
+                QCOMPARE(requestToken, o1.token().first);
+            if (!requestTokenSecret.isEmpty())
+                QCOMPARE(requestTokenSecret, o1.token().second);
+            tokenReceived = true;
+        }
+        else if (status == QAbstractOAuth::Status::Authenticated) {
+            if (!accessToken.isEmpty())
+                QCOMPARE(accessToken, o1.token().first);
+            if (!accessTokenSecret.isEmpty())
+                QCOMPARE(accessTokenSecret, o1.token().second);
+            tokenReceived = true;
+        }
+    });
+
+    QEventLoop loop;
+
+    QTimer::singleShot(10000, &loop, &QEventLoop::quit);
+    connect(&o1, &QOAuth1::granted, &loop, &QEventLoop::quit);
+    o1.grant();
+    loop.exec();
+    QVERIFY(tokenReceived);
+    QCOMPARE(o1.status(), QAbstractOAuth::Status::Authenticated);
+}
+
+void tst_QOAuth::authenticatedCalls_data()
+{
+    QTest::addColumn<QString>("consumerKey");
+    QTest::addColumn<QString>("consumerSecret");
+    QTest::addColumn<QString>("accessKey");
+    QTest::addColumn<QString>("accessKeySecret");
+    QTest::addColumn<QUrl>("url");
+    QTest::addColumn<QVariantMap>("parameters");
+    QTest::addColumn<QNetworkAccessManager::Operation>("operation");
+
+    const QVariantMap parameters { { QStringLiteral("first"), QStringLiteral("first") },
+                                   { QStringLiteral("second"), QStringLiteral("second") },
+                                   { QStringLiteral("third"), QStringLiteral("third") } };
+
+    QTest::newRow("term.ie_get") << "key"
+                                 << "secret"
+                                 << "accesskey"
+                                 << "accesssecret"
+                                 << QUrl("http://term.ie/oauth/example/echo_api.php")
+                                 << parameters
+                                 << QNetworkAccessManager::GetOperation;
+    QTest::newRow("term.ie_post") << "key"
+                                  << "secret"
+                                  << "accesskey"
+                                  << "accesssecret"
+                                  << QUrl("http://term.ie/oauth/example/echo_api.php")
+                                  << parameters
+                                  << QNetworkAccessManager::PostOperation;
+    QTest::newRow("oauthbin.com_get") << "key"
+                                      << "secret"
+                                      << "accesskey"
+                                      << "accesssecret"
+                                      << QUrl("http://oauthbin.com/v1/echo")
+                                      << parameters
+                                      << QNetworkAccessManager::GetOperation;
+    QTest::newRow("oauthbin.com_post") << "key"
+                                       << "secret"
+                                       << "accesskey"
+                                       << "accesssecret"
+                                       << QUrl("http://oauthbin.com/v1/echo")
+                                       << parameters
+                                       << QNetworkAccessManager::PostOperation;
+}
+
+void tst_QOAuth::authenticatedCalls()
+{
+    QFETCH(QString, consumerKey);
+    QFETCH(QString, consumerSecret);
+    QFETCH(QString, accessKey);
+    QFETCH(QString, accessKeySecret);
+    QFETCH(QUrl, url);
+    QFETCH(QVariantMap, parameters);
+    QFETCH(QNetworkAccessManager::Operation, operation);
+
+    QNetworkAccessManager networkAccessManager;
+    QNetworkReplyPtr reply;
+    QString receivedData;
+    QString parametersString;
+    {
+        bool first = true;
+        for (auto it = parameters.begin(), end = parameters.end(); it != end; ++it) {
+            if (first)
+                first = false;
+            else
+                parametersString += QLatin1Char('&');
+            parametersString += it.key() + QLatin1Char('=') + it.value().toString();
+        }
+    }
+
+    QOAuth1 o1(&networkAccessManager);
+    o1.setClientId(qMakePair(consumerKey, consumerSecret));
+    o1.setToken(qMakePair(accessKey, accessKeySecret));
+    QNetworkRequest request(url);
+    if (operation == QNetworkAccessManager::GetOperation)
+        reply.reset(o1.get(request, parameters));
+    else if (operation == QNetworkAccessManager::PostOperation)
+        reply.reset(o1.post(request, parameters));
+    QVERIFY(!reply.isNull());
+    QVERIFY(!reply->isFinished());
+
+    connect(&networkAccessManager, &QNetworkAccessManager::finished, [&receivedData](QNetworkReply *reply)
+    {
+        receivedData = QString::fromUtf8(reply->readAll());
+    });
+    QVERIFY(waitForFinish(reply) == Success);
+    QCOMPARE(receivedData, parametersString);
+    reply.clear();
+}
+
+QTEST_MAIN(tst_QOAuth)
+#include "tst_qoauth.moc"
diff --git a/tests/tests.pro b/tests/tests.pro
new file mode 100644
index 0000000000000000000000000000000000000000..1ee1b13235c47d8365bd094a1ca30ec8b280661f
--- /dev/null
+++ b/tests/tests.pro
@@ -0,0 +1,11 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+    qoauth
+
+for(p, SUBDIRS) {
+    X = $$find(p, INCLUDEPATH)
+    p.INCLUDEPATH += /TEST
+    message(Configuring test $${X})
+#    message(p)
+}