Pytest Scoping Issue with Fixture Indirect - pytest

I have the following:
// conftest.py
import pytest
#pytest.fixture(scope="session")
def deviceundertest(request):
return request.param
#pytest.fixture(scope="session")
def setupteardown(deviceundertest):
yield "conn"
#pytest.fixture(scope="session")
def data1(setupteardown):
return response
#pytest.fixture(scope="session")
def data2(setupteardown):
return response
// test_000.py
import pytest
#pytest.mark.parametrize("deviceundertest", ["server1", "server2"], indirect=True)
def test1(data1):
assert True
#pytest.mark.parametrize("deviceundertest", ["server3", "server4"], indirect=True)
def test2(data2):
assert True
// test_001.py
import pytest
#pytest.mark.parametrize("deviceundertest", ["server1", "server2"], indirect=True)
def test3(data1):
assert True
#pytest.mark.parametrize("deviceundertest", ["server3", "server4"], indirect=True)
def test4(data2):
assert True
When I run a --setup-plan and look at one of the fixtures Im seeing the fixtures setup and teardown is being performed more then its actual scoop (i.e session). Example below:
❯ pytest example_dir --setup-plan | grep "SETUP S deviceundertest"
SETUP S deviceundertest['server1']
SETUP S deviceundertest['server3']
SETUP S deviceundertest['server1']
SETUP S deviceundertest['server3']
SETUP S deviceundertest['server2']
SETUP S deviceundertest['server4']
SETUP S deviceundertest['server2']
SETUP S deviceundertest['server4']
Thanks,

Pytest doesn't check if the arguments are the same in multiple tests, every test is independent. The scope="session" is relevant only if the fixture is not invoked explicitly, which you are doing, so every parameter in each test will invoke the deviceundertest fixture.
You can implement a simple logic in conftest.py to use the same server instance in multiple tests
servers = {}
#pytest.fixture
def deviceundertest(request):
server = request.param
if server not in servers:
servers[server] = Server() # create your server instance
return servers[server]

You are sending different params to the root fixture through "#pytest.mark.parametrize", so it's creating a different fixture each time with the value you sent.
A fixture doesn't repeat the setup/teardown process only if it will 100% return the same value, in you case each time a different value can be menifested.

Related

Can pytest_sessionfinish still call the fixture with session as scope?

I need call one fixture when all the tests are successful. I tried to check if all tests are successful in pytest_sessionfinish. I also put my fixture with the session scope #pytest.fixture(scope="session"). However, the code in pytest_sessionfinish can not call this fixture at all. Are there some way to solve this issue?
#pytest.fixture(scope="session")
def myfunction(
ser: Serial, logger: Logger
) -> bool:
print("In my function")
...
#pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
print("Prepare to call myfunction")
myfunction
It only showes "Prepare to call myfunction" as the output.
Thanks!

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 I can't get value by using doReturn in Mockito

I had a method in mock service,
def whenDynamoDBActionBlacklist(newlist: List[String]) = {
doReturn(newlist).when(service).Blacklist
}
and want to test it by using
val list = mocks.whenDynamoDBActionBlacklist(List("333:avd"))
but I can't get the value, got the nullPointerException, can anyone help me with that? Thanks.
I believe you are confusing the difference between mock setup and mock execution. whenDynamoDBActionBlacklist does not actually return a value, instead in just specifies responses for the stubbed methods. Consider the following example
import org.mockito.{ArgumentMatchersSugar, IdiomaticMockito}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class MockitoScalaExampleSpec extends AnyFlatSpec with Matchers with IdiomaticMockito with ArgumentMatchersSugar {
"MockitoScalaExample" should "demonstrated difference between mock setup and mock execution" in {
// system under test
trait MyService {
def blacklist: List[String]
}
// initialise mock of the system under test
val serviceMock = mock[MyService]
// specify behaviour of the mock by defining how should stubbed methods respond
serviceMock.blacklist returns List("mocked!")
// actually execute the mocked system under test
val result = serviceMock.blacklist
// assert on the result
result should be(List("mocked!"))
}
}
The key is to understand
serviceMock.blacklist returns List("mocked!")
does not yet return List("mocked!") but it declares what should happen once we call aMock.blacklist.
Note the example uses mockito-scala but the same concepts apply in vanilla Mockito.

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'

Why can't the compiler find implicit ExecutionContext with Implicits.global imported?

I have the following piece of code:
import java.util.concurrent.Executor
import scala.concurrent.Future
trait Storage {
def store(location: String, content: Array[Byte])(implicit exec: Executor): Future[String]
}
object LocalStorage extends Storage {
override def store(location: String, content: Array[Byte])(implicit exec: Executor): Future[String] =
Future {
... do some stuff ...
"Hello"
}
}
And here comes the code for testing:
object LocalStorageTest extends App{
import scala.concurrent.ExecutionContext.Implicits.global
val testImage = AsyncWebClient get "some url"
val result = testImage.flatMap{content => LocalStorage.store("logo.png",content)}
val status =Await.result(result, 30 seconds)
println(status)
AsyncWebClient.shutDown
}
Whenever I try to run the code, I am getting the following error:
Cannot find an implicit ExecutionContext.
Why? Isn't scala.concurrent.ExecutionContext.Implicits.global in scope already? When this import is added directly to LocalStorage object, it works (!)
BUT I am loosing a possibility to alter the context. Since I do not want to run this code on global every time. As this is a part of an akka application and for "production" runtime it is supposed to run on some dispatcher. Just for testing I would like to run it on a global execution context.
Am I missing some important concept here or is my design wrong that I am loosing such flexibility? Where is the issue?!
An ExecutionContext is not the same thing as an Executor. You should make the method accept an implicit exec: ExecutionContext (scala.concurrent.ExecutionContext) and then it will behave as you expect.