Can not use autouse fixture to import module - pytest

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'

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

Pytest Scoping Issue with Fixture Indirect

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.

Patching class does not work its definition is get from dict

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

Perplexing unit test code execution order when using ScalaCheck / ScalaTest clauses

I am facing the following perplexing behaviour when unit testing classes with variables.
For the sake of a simple example, let's assume I have the following class:
// Case classes are not an alternative in my use case.
final class C(var i: Int = 0) {
def add(that: C): Unit = {
i += that.i
}
override def toString: String = {
s"C($i)"
}
}
For which I concocted the below trivial and seemingly harmless unit test:
import org.junit.runner.RunWith
import org.scalacheck.Gen
import org.scalatest.junit.JUnitRunner
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalatest.{MustMatchers, WordSpec}
#RunWith(classOf[JUnitRunner])
class CUnitTest extends WordSpec with MustMatchers with GeneratorDrivenPropertyChecks {
private val c: C = new C()
forAll (Gen.choose(1, 100).map(new C(_))) { x =>
s"Adding $x to $c" must {
val expectedI = c.i + x.i
c.add(x)
s"result in its .i property becoming $expectedI" in {
c.i mustBe expectedI
}
}
}
}
Where all test cases except the last fail:
For example, the first three test cases fail with the below results:
org.scalatest.exceptions.TestFailedException: 414 was not equal to 68
org.scalatest.exceptions.TestFailedException: 414 was not equal to 89
org.scalatest.exceptions.TestFailedException: 414 was not equal to 151
Now, playing around the unit test and moving the c.add(x) part inside the in clause:
import org.junit.runner.RunWith
import org.scalacheck.Gen
import org.scalatest.junit.JUnitRunner
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalatest.{MustMatchers, WordSpec}
#RunWith(classOf[JUnitRunner])
class CUnitTest extends WordSpec with MustMatchers with GeneratorDrivenPropertyChecks {
private val c: C = new C()
forAll (Gen.choose(1, 100).map(new C(_))) { x =>
s"Adding $x to $c" must {
val expectedI = c.i + x.i
s"result in its .i property becoming $expectedI" in {
c.add(x)
c.i mustBe expectedI
}
}
}
}
And all test cases except the first fail:
For example, the second and the third test cases fail with the following messages:
org.scalatest.exceptions.TestFailedException: 46 was not equal to 44
org.scalatest.exceptions.TestFailedException: 114 was not equal to 68
In addition, c.i doesn't seem to increase at all in the test case description as I intended and expected it to be.
Clearly, the order of execution inside ScalaTest clauses are not top-down. Something happens earlier or later than in the order it's written, or perhaps doesn't happen at all depending on which clause it is inside, yet I can't wrap my head around it.
What's going on and why?
Furthermore, how could I achieve the desired behaviour (c.i increasing, all test cases passing)?
Consider rewriting the test like so
import org.scalacheck.Gen
import org.scalatest._
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
class HelloSpec extends WordSpec with MustMatchers with ScalaCheckDrivenPropertyChecks {
private val c: C = new C()
"class C" must {
"add another class C" in {
forAll (Gen.choose(1, 100).map(new C(_))) { x =>
val expectedI = c.i + x.i
c.add(x)
c.i mustBe expectedI
}
}
}
}
Note how here forAll is on the "inside" of the test body which means we have a single test which is using multiple inputs provided by forAll to test the system C. When it is on the "outside" like so
forAll (Gen.choose(1, 100).map(new C(_))) { x =>
s"Adding $x to $c" must {
...
s"result in its .i property becoming $expectedI" in {
...
}
}
}
then forAll is misused to generate multiple tests where each has a single test input, however the purpose of forAll is to generate multiple inputs for system under test, not multiple tests. Furthermore, design of CUnitTest results in subsequent test depending on the state of the previous test which is buggy and harder to maintain. Ideally tests would run in isolation of each other where all the state needed is provided afresh as part of a test fixture.
Few side notes: #RunWith(classOf[JUnitRunner]) should not be necessary, and GeneratorDrivenPropertyChecks is deprecated.

Possible to rename a single type after import

I have a situation where I want to include all types from a package. But one of these types clashes with my own type. Is it possible to import the whole package and then rename the type that clashes, after the fact, rather than individually import every type I need?
import somepackage.all._ // Contains A, B, C, etc
import somepackage.all.{A=>_A} // Can I change the name of A after import?
class A {
val a = new _A()
val b = new B()
val c = new C()
}
You can do both at the same time:
import somepackage.all.{A=>_A, _}
This will import everything except A which will be imported as _A instead.