Patching class does not work its definition is get from dict - pytest

I have a problem with patching, how can I patch A class properly?
a.py
class A:
pass
plugin.py
from a import A
CONFIG = {'aenum': A}
def do():
instance = CONFIG['aenum']() # the A class is NOT patched using this
# instance = A() # the test passes with this
return instance.auth()
test.py (using pytest to run)
from unittest.mock import Mock, patch
import plugin
def test_do():
a_instance_mock = Mock()
a_instance_mock.auth.return_value = 42
with patch("plugin.A", return_value=a_instance_mock):
assert plugin.do() == 42

patch overrides only the specific item from given namespace; the class name is patched AFTER a module is imported, not in the importation time.
That is why the CONFIG["aenum"] is left untouched.
Use patch.dict instead
from unittest.mock import Mock, patch
from plugin import do
def test_do():
a_instance_mock = Mock()
a_instance_mock.authenticate.return_value = 42
a_class_mock = Mock(return_value=a_instance_mock)
with patch.dict("plugin.CONFIG", {"aenum": a_class_mock}):
assert do() == 42

Related

How to pytest monkeypatch multiple argv arguments?

I'm only able to test one argument e.g. animal. Every other added argument fails e.g. name. What must I change?
pytest foo.py returns pytest: error: the following arguments are required: name
### foo.py ###
import argparse
import pytest
import sys
#pytest.mark.parametrize('animal_input', ['cat'])
#pytest.mark.parametrize('name_input', ['tom'])
def test_get_bake_progress(monkeypatch, animal_input, name_input):
with monkeypatch.context() as m:
m.setattr(sys, 'argv', ['--animal', animal_input])
m.setattr(sys, 'argv', ['--name', name_input])
assert foo() == (animal_input)
def foo():
parser = argparse.ArgumentParser()
parser.add_argument('animal')
parser.add_argument('name')
args = parser.parse_args()
animal = args.animal
name = args.name
return animal

How to get result of ammonite script executed via ammonite.Main().runScript()?

I want to run an ammonite Scala script from an application and get the result:
object TestMain extends App {
val path = os.Path("scripts/ammtest/Test.sc", base = os.pwd)
val res = ammonite.Main().runScript(path, Nil)
println(s"res = $res")
}
However this only works if the script is wrapped in a #main annotation:
Test.sc:
#main def main(): Int = {
42
}
Is there any way to avoid having to include the #main def main() part?
I would like the syntax to use a DSL that returns an instance of a class.
The returned class instance is needed by the application that is starting the script.

Using pytest patch decorator to test ray actors remote function

I'm trying to run unit test for a ray remote function. I am using a #patch decorator to patch the remote function. The
foo.py
class Foo(object):
def __init__(self):
self.value = 0
def bar(self):
self.value = 100
print("In original method")
assert False
test_foo.py
from unittest.mock import patch
import pytest
import unittest
import ray
from tests.foo import Foo
#pytest.fixture
def ray_fixture():
print("Initializing ray")
if not ray.is_initialized():
ray.init()
yield None
print("Terminating ray")
ray.shutdown()
def fake_bar(self):
print("In fake method")
assert True
#pytest.mark.usefixtures("ray_fixture")
class FooTestCase(unittest.TestCase):
"""Test cases for Foo module"""
#patch("foo.Foo.bar", new=fake_bar)
def test_bar(self):
Foo().bar()
#patch("foo.Foo.bar", new=fake_bar)
def test_bar_remote(self):
foo_actor = ray.remote(Foo).remote()
obj_ref = foo_actor.bar.remote()
ray.get(obj_ref)
The test test_bar passes and test_bar_remote fails.
If I use ray.init(local_mode=True) then both tests pass. I can not use local_mode=True due to other limitations.
How can we patch ray actor's remote method using #patch?
Here's an alternative. Subclass Foo with a stubbed/mocked implementation and use it in ray. That way, the Foo class would be intact, you would only update those that needs to be mocked e.g. the method bar().
test_foo.py
...
class FooStub(Foo):
def bar(self, *args, **kwargs):
print("In another fake method")
assert True
# Optionally, you can also call the real method if you want. You may update the arguments as needed.
# super().bar(*args, **kwargs)
#pytest.mark.usefixtures("ray_fixture")
class FooTestCase(unittest.TestCase):
...
def test_bar_remote(self):
foo_actor = ray.remote(FooStub).remote()
obj_ref = foo_actor.bar.remote()
ray.get(obj_ref)
...
Output
$ pytest -q -rP
..
================================================================================================= PASSES ==================================================================================================
__________________________________________________________________________________________ FooTestCase.test_bar ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------------------
Initializing ray
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
In fake method
---------------------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------------------
Terminating ray
_______________________________________________________________________________________ FooTestCase.test_bar_remote _______________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------------------
Initializing ray
---------------------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------------------
Terminating ray
2 passed, 1 warning in 5.03s
I have found a below hacky way which involves change in original function (and check env variable to provide test implementation)
import os
class Foo(object):
def __init__(self):
self.value = 0
def bar(self):
self.value = 100
if os.environ.get['TEST'] == 'True':
print("In fake method")
assert True
else:
print("In original method")
assert False
runtime_env = {"env_vars": {"TEST": "True"}}
ray.remote(Foo) .options(runtime_env=runtime_env) .remote()

Why does pureconfig not find my implicit readers?

I use double to encode boolean value in a configuration file. PureConfig does not find a way to cast it while reading the configuration.
Initial question (see below for edit).
Here is some code to reproduce the behavior.
import com.typesafe.config.ConfigFactory
import pureconfig.ConfigReader
import pureconfig.generic.auto._
object Main {
def main(args: Array[String]): Unit = {
println(pureconfig.loadConfig[BooleanTest](ConfigFactory.parseString("a = 1")))
}
}
case class BooleanTest(a: Boolean)
object ConfigImplicits {
implicit val myBooleanReader: ConfigReader[Boolean] = ConfigReader[Double].map { n => n > 0}
}
Here, I expect my code to print an instance of BooleanTest.
Instead, I got a ConvertFailure:
Left(ConfigReaderFailures(ConvertFailure(WrongType(NUMBER,Set(BOOLEAN)),None,a),List()))
One way to fix this, is to add import ConfigImplicits._ just before calling the loadConfig function.
However, as you can suppose, my code is actually part of a bigger project, and adding the import in the real project does not fix my error.
Do you have any hint on what can be wrong?
Kind,
Alexis.
Edit:
After comments from Thilo it appears logic to add the import statement.
Below is an updated version of the code which include the import statement but still produce the same error...
Change the main function to:
def main(args: Array[String]): Unit = {
println(ConfigUtils.loadConfig[BooleanTest]("a = 1"))
}
And declare a ConfigUtils object as follow:
object ConfigUtils {
def loadConfig[A : ConfigReader](str: String) : ConfigReader.Result[A] = {
import ConfigImplicits._
val config = ConfigFactory.parseString(str)
pureconfig.loadConfig[A](config)
}
}
Run the code and you get the same error as previously:
ConvertFailure(WrongType(NUMBER,Set(BOOLEAN))
Why does pureconfig not use my implicit myBooleanReader to parse this configuration?
Kind, Alexis.

Can not use autouse fixture to import module

I have a test module has one autouse fixture
import pytest
#pytest.fixture(autouse=True):
def set_env_config(monkeypatch):
palladium_config = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), 'config.py')
monkeypatch.setenv('PALLADIUM_CONFIG', palladium_config)
from A import B
and in every followed test within this test module class B is needed, but this importation can not be achieved for any tests.
In the other way, I patch only the environment variable only
#pytest.fixture(autouse=True):
def set_env_config(monkeypatch):
palladium_config = os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), 'config.py')
monkeypatch.setenv('PALLADIUM_CONFIG', palladium_config)
and import the class B in every test case, it succeeded.
Why is that ? why can't I import class within autouse fixture
thanks a lot
I am guessing that you are expecting something like the following:
#pytest.fixture(autouse=True)
def do_an_import():
from A import B
def test_foo():
assert B.do_my_thing() == 'foo'
That doesn't work, while doing the following does what you want:
def test_foo():
from A import B
assert B.do_my_thing() == 'foo'
Unfortunately doing an import in the fixture (the first example) will add B into that fixture function's namespace, but not the test function's namespace.
Similarly, this wouldn't work for the same reason:
#pytest.fixture
def not_an_autouse_fixture():
from A import B
def test_foo(not_an_autouse_fixture):
assert B.do_my_thing() == 'foo'
B is being imported into the fixture's namespace, which is different from the test's namespace. You could instead do:
#pytest.fixture
def Cls():
from A import B
return B
def test_foo(Cls):
assert Cls.do_my_thing() == 'foo'
or you could import it at the top level of your module like:
from A import B
def test_foo(B):
assert B.do_my_thing() == 'foo'