I have a class used to authenticate with google sheet API and retrieve data from some spreadsheets.
Here a part of it:
spreadsheet.py
from typing import Optional
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
import gspread
class GoogleSheet:
def __init__(self, token_file: str):
self.token_file: str = token_file
self.google_client: Optional[gspread.Client] = None
self.gsheet: Optional[gspread.Spreadsheet] = None
def setup_connection(self) -> None:
credentials: Credentials = Credentials.from_authorized_user_file(self.token_file)
if credentials.expired:
credentials.refresh(Request())
self.google_client = gspread.authorize(credentials)
def open_gsheet_by_url(self, gsheet_url: str) -> None:
self.gsheet = self.google_client.open_by_url(gsheet_url)
I wanted to create some tests for the previous code.
Here is what I ended to:
test_spreadsheet.py
import pytest
from spreadsheet import GoogleSheet
from unittest.mock import patch
class TestSpreadSheetData:
#patch('spreadsheet.Credentials')
#patch('spreadsheet.gspread')
def test_setup_connection(self, mocked_creds, mocked_auth):
google_sheet = GoogleSheet('api_token.json')
google_sheet.setup_connection()
assert mocked_creds.from_authorized_user_file.call_count == 1
assert mocked_auth.authorize.call_count == 1
I tried the code above but it didn't work although I had similar approach with different project and packages.
So whenever I run the test, I get the following error:
AssertionError
Assert 0 == 1
Can anyone helps me fix this?
Related
I created a code to invoke a AWS lambda function created somewhere else.
I would like to use moto for testing it, but I don't really understand how to do it and I continue to obtain errors.
This is a simple example of the main code:
import boto3
import json
class SimpleLambda:
def __init__(self):
self.aws_lambda = boto3.client("lambda", region_name="eu-west-2")
def __call__(self):
try:
lambda_response = self.aws_lambda.invoke(
FunctionName="test-lambda",
Payload=json.dumps(
{
"Records": [
{
"Source": "test_source",
"Version": "test_version",
}
]
}
),
)
return lambda_response["Payload"].read()
except Exception as err:
print(f"Could not invoke simple lambda: {err}")
return None
and the test:
import os
import pytest
import unittest.mock as mock
import boto3
from moto import mock_lambda
from aws_lambda import SimpleLambda
#pytest.fixture
def aws_credentials():
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
os.environ["AWS_SECURITY_TOKEN"] = "testing"
os.environ["AWS_SESSION_TOKEN"] = "testing"
#pytest.fixture
def lambda_client(aws_credentials):
with mock_lambda():
yield boto3.client("lambda", region_name="eu-west-2")
#pytest.fixture
def lambda_test(lambda_client):
lambda_client.create_function(
FunctionName="test-lambda",
Role="arn:aws:iam::123456789012:role/doesnotexist",
Code={"ZipFile": b"test"}
)
yield
def test_simple_lambda_call(lambda_client, lambda_test):
simple_lambda = SimpleLambda()
test = simple_lambda()
I obtain the error:
botocore.errorfactory.InvalidParameterValueException: An error occurred (InvalidParameterValueException) when calling the CreateFunction operation: The role defined for the function cannot be assumed by Lambda.
I found several example about how to use moto with S3 bucket, but nothing with lambda.
Running the code in this other question, I obtain the same error.
Any advice?
Moto also validates whether the IAM role exists, just like AWS does.
So make sure that the IAM role is created first:
with mock_iam():
iam = boto3.client("iam", region_name="eu-west-2")
iam_role = iam.create_role(
RoleName="my-role",
AssumeRolePolicyDocument="some policy",
Path="/my-path/",
)["Role"]["Arn"]
I was mocking a function that is used to read k8s secret to fetch secret token. But running unittest is creating error - AttributeError: <module 'kubernetes.client' from '/usr/lib/python3.6/site-packages/kubernetes/client/init.py'> does not have the attribute 'read_namespaced_secret()' I have gone through How do you mock Python Kubernetes client CoreV1Api , but its also not helping my case. Can anyone point out what I am doing wrong here?
My script - read_sec.py
import base64
from kubernetes import client, config
from logger import logger
class kubernetesServices():
def __init__(self):
pass
def get_secret_vault_token(self):
try:
config.load_kube_config()
api_instance = client.CoreV1Api()
sec = api_instance.read_namespaced_secret("random-sec", "random-ns").data
token = base64.b64decode(sec['token']).decode("utf-8")
return token
except Exception as e:
logger.error("got error at get_secret_vault_token: {}".format(str(e)))
Unittest - test_read_sec.py
import unittest
from unittest.mock import patch
from read_sec import *
class MockKubernetes():
def __init__(self):
pass
def mocker_read_namespaced_secret(*args, **kwargs):
class MockReadns():
def __init__(self, json_data):
self.json_data = json_data
def json(self):
return self.json_data
return MockReadns({"data":{"token":"abc123"}})
class TestkubernetesServices(unittest.TestCase):
#patch("kubernetes.client",side_effect=MockKubernetes)
#patch("kubernetes.config",side_effect=MockKubernetes)
#patch("kubernetes.client.read_namespaced_secret()",side_effect=mocker_read_namespaced_secret)
def test_get_secret_vault_token(self,mock_client,mock_config,mock_read):
k8s = kubernetesServices()
token = k8s.get_secret_vault_token()
You need to mock kubernetes.client.CoreV1Api instead of kubernetes.client. Here is an example:
import base64
import unittest
from unittest.mock import patch, Mock
import requests
from kubernetes import client, config
class kubernetesServices():
def get_secret_vault_token(self):
config.load_kube_config()
api_instance = client.CoreV1Api()
sec = api_instance.read_namespaced_secret('random-sec', 'random-ns').data
token = base64.b64decode(sec['token']).decode('utf-8')
return token
class TestkubernetesServices(unittest.TestCase):
#patch(
'kubernetes.client.CoreV1Api',
return_value=Mock(read_namespaced_secret=Mock(return_value=Mock(data={'token': b'YWJjMTIz'})))
)
#patch('kubernetes.config.load_kube_config', return_value=Mock())
def test_get_secret_vault_token(self, mock_client, mock_config):
k8s = kubernetesServices()
token = k8s.get_secret_vault_token()
self.assertEqual(token, 'abc123')
Result:
---------------------------------------------------------------------
Ran 1 tests in 0.071s
PASSED (successes=1)
JFYI: side_effect better to use when you need multiple results. Example:
class TestRequest(unittest.TestCase):
def test_side_effect(self):
with patch('requests.get', side_effect=[1, 2, 3]):
print(requests.get('url1')) # 1
print(requests.get('url2')) # 2
print(requests.get('url3')) # 3
The following examples both work just fine, the only issue is that mypy is complaing about create_operation.
Specifically I'm getting these errors:
Variable "model" is not valid as a type
model? has no attribute "dict"
Especially the second error doesn't make sense to me since pydantic.BaseModel definitely has a dict method. Is there a better way to annotate this?
from typing import Type
from pydantic import BaseModel
from fastapi import FastAPI, testclient
app = FastAPI()
client = testclient.TestClient(app)
class A(BaseModel):
foo: str
# regular way of creating an endpoint
#app.post("/foo")
def post(data: A):
assert data.dict() == {"foo": "1"}
# creating an endpoint programmatically
def create_operation(model: Type[BaseModel]):
#app.post("/bar")
def post(data: model):
assert data.dict() == {"foo": "1"}
create_operation(A)
assert client.post("/foo", json={"foo": 1}).status_code == 200
assert client.post("/bar", json={"foo": 1}).status_code == 200
Is it possible to patch over a class instance variable and force it to return a different value each time that it's referenced? specifically, I'm interested in doing this with the side_effect parameter
I know that when patching over a method it is possible to assign a side_effect to a mock method. If you set the side_effect to be a list it will iterate through the list returning a different value each time it is called.
I would like to do the same thing with a class instance variable but cannot get it to work and I cannot see any documentation to suggest whether this is or is not possible
Example
from unittest.mock import patch
def run_test():
myClass = MyClass()
for i in range(2):
print(myClass.member_variable)
class MyClass():
def __init__(self):
self.member_variable = None
#patch('test_me.MyClass.member_variable',side_effect=[1,2], create=True)
def test_stuff(my_mock):
run_test()
assert False
Output
-------------- Captured stdout call ---------------------------------------------------------------------------------------------------------------------
None
None
Desired Output
-------------- Captured stdout call ---------------------------------------------------------------------------------------------------------------------
1
2
To be clear - I'm aware that I can wrap member_variable in a get_member_variable method(). That is not my question. I just want to know if you can patch a member variable with a side_effect.
side_effect can be either a function, an iterable or an exception (https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.side_effect). I think that's the reason why it's not working.
Another way to test this would be:
>>> class Class:
... member_variable = None
...
>>> with patch('__main__.Class') as MockClass:
... instance = MockClass.return_value
... instance.member_variable = 'foo'
... assert Class() is instance
... assert Class().member_variable == 'foo'
...
Here's the docs: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch
In the case of the example you set I could not make the change the way I thought, you may have more items in this class and with this idea can help you.
Not the best option to side effect on an attribute but it worked as I needed it.
PS: I ended up putting as an example the code that brought me to your question.
Example:
# -*- coding: utf-8 -*-
# !/usr/bin/env python3
import requests
from src.metaclasses.singleton import Singleton
from src.services.logger import new_logger
from src.exceptions.too_many_retries import TooManyRetries
from src.exceptions.unavailable_url import UnavailableURL
LOG = new_logger(__name__)
class PostbackService(metaclass=Singleton):
def __init__(self):
self.session = requests.session()
def make_request(self, method, url, headers, data=None, retry=0):
r = self.session.request(method, url, data=data, headers=headers)
if r.status_code != 200:
if retry < 3:
return self.make_request(method, url, headers, data, retry + 1)
message = f"Error performing request for url: {url}"
LOG.error(message)
raise TooManyRetries(message)
return r.json()
Test:
# -*- coding: utf-8 -*-
# !/usr/bin/env python3
from unittest import TestCase
from unittest.mock import patch, MagicMock
from src.services.postback import PostbackService
from src.exceptions.too_many_retries import TooManyRetries
from src.exceptions.unavailable_url import UnavailableURL
class TestPostbackService(TestCase):
#patch("src.services.postback.requests")
def setUp(self, mock_requests) -> None:
mock_requests.return_value = MagicMock()
self.pb = PostbackService()
def test_make_request(self):
self.pb.session.request.return_value = MagicMock()
url = "http://mock.io"
header = {"mock-header": "mock"}
data = {"mock-data": "mock"}
mock_json = {"mock-json": "mock"}
def _def_mock(value):
"""
Returns a MagicMock with the status code changed for each request, so you can test the retry behavior of the application.
"""
mock = MagicMock()
mock.status_code = value
mock.json.return_value = mock_json
return mock
self.pb.session.request.side_effect = [
_def_mock(403),
_def_mock(404),
_def_mock(200),
]
self.assertEqual(self.pb.make_request("GET", url, header, data), mock_json)
self.pb.session.request.side_effect = [
_def_mock(403),
_def_mock(404),
_def_mock(404),
_def_mock(404),
]
with self.assertRaises(TooManyRetries):
self.pb.make_request("GET", url, header, data)
As you can see I recreated magicmock by changing the side effect of each one to what I wanted to do. It was not beautiful code and super pythonic, but it worked as expected.
I used as base to create this magicmock object the link that #rsarai sent from the unittest documentation.
I'm trying to patch an API for unit testing purposes. I have a class like this:
# project/lib/twilioclient.py
from os import environ
from django.conf import settings
from twilio.rest import TwilioRestClient
def __init__(self):
return super(Client, self).__init__(
settings.TWILIO_ACCOUNT_SID,
settings.TWILIO_AUTH_TOKEN,
)
client = Client()
and this method:
# project/apps/phone/models.py
from project.lib.twilioclient import Client
# snip imports
class ClientCall(models.Model):
'call from Twilio'
# snip model definition
def dequeue(self, url, method='GET'):
'''"dequeue" this call (take off hold)'''
site = Site.objects.get_current()
Client().calls.route(
sid=self.sid,
method=method,
url=urljoin('http://' + site.domain, url)
)
and then the following test:
# project/apps/phone/tests/models_tests.py
class ClientCallTests(BaseTest):
#patch('project.apps.phone.models.Client', autospec=True)
def test_dequeued(self, mock_client):
cc = ClientCall()
cc.dequeue('test', 'TEST')
mock_client.calls.route.assert_called_with(
sid=cc.sid,
method='TEST',
url='http://example.com/test',
)
When I run the test, it fails because the real client is called instead of the mock client.
Looks like it should work to me, and I've been able to make this type of thing work before. I've tried the usual suspects (removing *.pyc) and things don't seem to be changing. Is there a new behavior here that I'm missing somehow?