This page documents Pyvider's testing utilities and best practices for testing providers.
🤖 AI-Generated Content
This documentation was generated with AI assistance and is still being audited. Some, or potentially a lot, of this information may be inaccurate. Learn more.
importpytestfrommy_providerimportMyCloudProvider@pytest.fixturedefprovider():"""Create a configured provider instance."""returnMyCloudProvider()@pytest.fixtureasyncdefconfigured_provider(provider):"""Create and configure a provider."""config=MyCloudProvider.Config(api_key="test-key",region="us-east-1")awaitprovider.configure(config)returnprovider
importpytestfrompyvider.exceptionsimportValidationError@pytest.mark.asyncioasyncdeftest_resource_validates_required_fields(instance_resource):"""Test that required fields are validated."""# Note: In practice, attrs will raise TypeError for missing required fields# This example shows conceptual validation testingwithpytest.raises((ValidationError,TypeError)):ctx=make_context(config=InstanceConfig(# Missing required 'name' field - this will fail at constructionsize="t2.micro",ami="ami-12345678",))awaitinstance_resource._create_apply(ctx)@pytest.mark.asyncioasyncdeftest_resource_validates_field_values(instance_resource):"""Test that field values are validated."""ctx=make_context(config=InstanceConfig(name="test",size="invalid-size",# Invalid sizeami="ami-12345678",))withpytest.raises(ValidationError):awaitinstance_resource._create_apply(ctx)
importpytestimportattrsfrommy_provider.data_sourcesimportImage# Example attrs classes for Image data source@attrs.defineclassImageConfig:"""Image lookup configuration."""name_filter:strmost_recent:bool=True@attrs.defineclassImageData:"""Image data."""id:strname:strcreated_at:str@pytest.fixturedefimage_data_source():"""Create an image data source."""returnImage()@pytest.mark.asyncioasyncdeftest_data_source_read(image_data_source):"""Test data source read."""config=ImageConfig(name_filter="ubuntu-22.04",most_recent=True)data=awaitimage_data_source.read(config)assertdata.idisnotNoneassert"ubuntu"indata.name.lower()assertdata.created_atisnotNone@pytest.mark.asyncioasyncdeftest_data_source_filters(image_data_source):"""Test data source filtering."""config=ImageConfig(name_filter="ubuntu*",most_recent=True)data=awaitimage_data_source.read(config)assertdata.idisnotNoneassert"ubuntu"indata.name.lower()
importpytestimportattrsfrommy_provider.functionsimportHashFunction# Example attrs classes for HashFunction@attrs.defineclassHashParameters:"""Hash function parameters."""input:stralgorithm:str="sha256"@attrs.defineclassHashResult:"""Hash function result."""output:str@pytest.fixturedefhash_function():"""Create a hash function."""returnHashFunction()@pytest.mark.asyncioasyncdeftest_function_call(hash_function):"""Test function execution."""params=HashParameters(input="hello world",algorithm="sha256")result=awaithash_function.call(params)assertresult.outputisnotNoneassertlen(result.output)==64# SHA256 produces 64 hex chars@pytest.mark.asyncioasyncdeftest_function_algorithms(hash_function):"""Test different hash algorithms."""params_sha256=HashParameters(input="test",algorithm="sha256")result_sha256=awaithash_function.call(params_sha256)assertlen(result_sha256.output)==64params_md5=HashParameters(input="test",algorithm="md5")result_md5=awaithash_function.call(params_md5)assertlen(result_md5.output)==32# MD5 produces 32 hex chars
importpytestimportattrsfrommy_provider.ephemeralsimportToken# Example attrs classes for Token ephemeral resource@attrs.defineclassTokenConfig:"""Token configuration."""scope:strttl:int=3600@attrs.defineclassTokenData:"""Token data."""token:strexpires_at:str@pytest.fixturedeftoken_ephemeral():"""Create a token ephemeral resource."""returnToken()@pytest.mark.asyncioasyncdeftest_ephemeral_open(token_ephemeral):"""Test ephemeral resource open."""config=TokenConfig(scope="read:write",ttl=3600)data=awaittoken_ephemeral.open(config)assertdata.tokenisnotNoneassertdata.expires_atisnotNone@pytest.mark.asyncioasyncdeftest_ephemeral_renew(token_ephemeral):"""Test ephemeral resource renewal."""config=TokenConfig(scope="read:write",ttl=3600)# Openoriginal_data=awaittoken_ephemeral.open(config)# Renewrenewed_data=awaittoken_ephemeral.renew(config,original_data)assertrenewed_data.token==original_data.token# Expiration time should be extendedassertrenewed_data.expires_at>=original_data.expires_at@pytest.mark.asyncioasyncdeftest_ephemeral_close(token_ephemeral):"""Test ephemeral resource close."""config=Token.Config(scope="read:write",ttl=3600)# Opendata=awaittoken_ephemeral.open(config)# Close (should not raise)awaittoken_ephemeral.close(data)
importpytestfromunittest.mockimportAsyncMock,MagicMock@pytest.mark.asyncioasyncdeftest_resource_with_mocked_api(instance_resource,mocker):"""Test resource with mocked API calls."""# Mock the API clientmock_client=AsyncMock()mock_client.create_instance.return_value={"id":"i-12345","public_ip":"203.0.113.42","status":"running"}# Inject mock into resourceinstance_resource.client=mock_client# Test createctx=make_context(config=Instance.Config(name="test-instance",size="t2.micro",ami="ami-12345678",))state,_=awaitinstance_resource._create_apply(ctx)# Verify mock was calledmock_client.create_instance.assert_called_once()assertstateandstate.id=="i-12345"
importpytestimportresponses@responses.activate@pytest.mark.asyncioasyncdeftest_data_source_with_http_mock(image_data_source):"""Test data source with mocked HTTP responses."""# Mock HTTP responseresponses.add(responses.GET,"https://api.mycloud.com/v1/images",json={"images":[{"id":"ami-12345678","name":"ubuntu-22.04","created_at":"2024-01-01T00:00:00Z"}]},status=200)config=Image.Config(name_filter="ubuntu-22.04",most_recent=True)data=awaitimage_data_source.read(config)assertdata.id=="ami-12345678"assertdata.name=="ubuntu-22.04"
importpytestfromhypothesisimportgiven,strategiesasst@given(name=st.text(min_size=1,max_size=50),size=st.sampled_from(["t2.micro","t2.small","t3.medium"]))@pytest.mark.asyncioasyncdeftest_resource_handles_various_inputs(instance_resource,name,size):"""Test resource with property-based testing."""ctx=make_context(config=Instance.Config(name=name,size=size,ami="ami-12345678",))state,_=awaitinstance_resource._create_apply(ctx)assertstateisnotNoneassertstate.status=="running"
importpytestfrommy_providerimportMyCloudProvider@pytest.fixture(scope="session")defprovider_metadata():"""Provider metadata for tests."""return{"name":"mycloud","version":"1.0.0"}@pytest.fixturedefprovider():"""Create a provider instance."""returnMyCloudProvider()@pytest.fixtureasyncdefconfigured_provider(provider):"""Create a configured provider."""config=MyCloudProvider.Config(api_key="test-key",region="us-east-1")awaitprovider.configure(config)returnproviderdefpytest_configure(config):"""Configure pytest with custom markers."""config.addinivalue_line("markers","integration: mark test as integration test")config.addinivalue_line("markers","slow: mark test as slow running")
# Run all testsuvrunpytest
# Run specific test fileuvrunpytesttests/unit/test_resources.py
# Run specific testuvrunpytesttests/unit/test_resources.py::test_resource_create
# Run with coverageuvrunpytest--cov=my_provider
# Run integration tests onlyuvrunpytest-mintegration
# Run tests in paralleluvrunpytest-nauto