add mock providers for testing framework (#1772)

Signed-off-by: ollevche <ollevche@gmail.com>
This commit is contained in:
Oleksandr Levchenkov 2024-07-09 14:41:52 +03:00 committed by GitHub
parent 2c5c8a5f72
commit 9d9a7aab06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 716 additions and 143 deletions

View File

@ -7,6 +7,7 @@ NEW FEATURES:
* Added support for `override_resource`, `override_data` and `override_module` blocks in testing framework. ([#1499](https://github.com/opentofu/opentofu/pull/1499))
* Variables and Locals allowed in module sources and backend configurations (with limitations) ([#1718](https://github.com/opentofu/opentofu/pull/1718))
* Added support to new .tofu extensions to allow tofu-specific overrides of .tf files ([#1738](https://github.com/opentofu/opentofu/pull/1738))
* Added support for `mock_provider`, `mock_resource` and `mock_data` blocks in testing framework. ([#1772](https://github.com/opentofu/opentofu/pull/1772))
ENHANCEMENTS:
* Added `tofu test -json` types to website Machine-Readable UI documentation. ([#1408](https://github.com/opentofu/opentofu/issues/1408))

View File

@ -52,8 +52,8 @@ func TestMultipleRunBlocks(t *testing.T) {
}
}
func TestOverrides(t *testing.T) {
// This test fetches "local" and "random" providers.
func TestMocksAndOverrides(t *testing.T) {
// This test fetches providers from registry.
skipIfCannotAccessNetwork(t)
tf := e2e.NewBinary(t, tofuBin, filepath.Join("testdata", "overrides-in-tests"))
@ -76,7 +76,7 @@ func TestOverrides(t *testing.T) {
if stderr != "" {
t.Errorf("unexpected stderr output on 'test':\n%s", stderr)
}
if !strings.Contains(stdout, "11 passed, 0 failed") {
if !strings.Contains(stdout, "12 passed, 0 failed") {
t.Errorf("output doesn't have expected success string:\n%s", stdout)
}
}

View File

@ -57,3 +57,28 @@ module "rand_count" {
source = "./rand"
}
resource "aws_s3_bucket" "test" {
bucket = "must not be used anyway"
}
data "aws_s3_bucket" "test" {
bucket = "must not be used anyway"
}
provider "local" {
alias = "aliased"
}
resource "local_file" "mocked" {
provider = local.aliased
filename = "mocked.txt"
content = "I am mocked file, do not create me please"
}
data "local_file" "maintf" {
provider = local.aliased
filename = "main.tf"
}
resource "random_pet" "cat" {}

View File

@ -221,3 +221,67 @@ run "check_for_each_n_count_overridden" {
error_message = "Mocked random integer should be 101"
}
}
# ensures non-aliased provider is mocked by default
mock_provider "aws" {
mock_resource "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::mocked"
}
}
mock_data "aws_s3_bucket" {
defaults = {
bucket_domain_name = "mocked.com"
}
}
}
# ensures non-aliased provider works as intended
# and aliased one is mocked
mock_provider "local" {
alias = "aliased"
}
# ensures we can use this provider in run's providers block
# to use mocked one only for a specific test
mock_provider "random" {
alias = "for_pets"
mock_resource "random_pet" {
defaults = {
id = "my lovely cat"
}
}
}
run "check_mock_providers" {
assert {
condition = resource.aws_s3_bucket.test.arn == "arn:aws:s3:::mocked"
error_message = "aws s3 bucket resource doesn't have mocked values"
}
assert {
condition = data.aws_s3_bucket.test.bucket_domain_name == "mocked.com"
error_message = "aws s3 bucket data doesn't have mocked values"
}
assert {
condition = !fileexists(local_file.mocked.filename)
error_message = "file should not be created due to provider being mocked"
}
assert {
condition = data.local_file.maintf.content != file("main.tf")
error_message = "file should not be read due to provider being mocked"
}
providers = {
random = random.for_pets
}
assert {
condition = resource.random_pet.cat.id == "my lovely cat"
error_message = "providers block in run should allow replacing real providers by mocked"
}
}

View File

@ -928,6 +928,8 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
// 3b. If the run has no override configuration, we copy all the providers
// from the test file into `next`, overriding all providers with name
// collisions from the original config.
// 3c. Copy all mock providers from the test file to the `next`, overriding
// providers with name collisions from the original config.
// 4. We then modify the original configuration so that the providers it
// holds are the combination specified by the original config, the test
// file and the run file.
@ -951,7 +953,7 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
for _, ref := range run.Providers {
testProvider, ok := file.Providers[ref.InParent.String()]
testProvider, ok := file.getTestProviderOrMock(ref.InParent.String())
if !ok {
// Then this reference was invalid as we didn't have the
// specified provider in the parent. This should have been
@ -966,13 +968,15 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
}
next[ref.InChild.String()] = &Provider{
Name: ref.InChild.Name,
NameRange: ref.InChild.NameRange,
Alias: ref.InChild.Alias,
AliasRange: ref.InChild.AliasRange,
Version: testProvider.Version,
Config: testProvider.Config,
DeclRange: testProvider.DeclRange,
Name: ref.InChild.Name,
NameRange: ref.InChild.NameRange,
Alias: ref.InChild.Alias,
AliasRange: ref.InChild.AliasRange,
Version: testProvider.Version,
Config: testProvider.Config,
DeclRange: testProvider.DeclRange,
IsMocked: testProvider.IsMocked,
MockResources: testProvider.MockResources,
}
}
@ -984,6 +988,18 @@ func (c *Config) transformProviderConfigsForTest(run *TestRun, file *TestFile) (
}
}
for _, mp := range file.MockProviders {
next[mp.moduleUniqueKey()] = &Provider{
Name: mp.Name,
NameRange: mp.NameRange,
Alias: mp.Alias,
AliasRange: mp.AliasRange,
DeclRange: mp.DeclRange,
IsMocked: true,
MockResources: mp.MockResources,
}
}
c.Module.ProviderConfigs = next
return func() {

View File

@ -1,8 +1,10 @@
package hcl2shim
import (
"cmp"
"fmt"
"math/rand"
"slices"
"strings"
"github.com/opentofu/opentofu/internal/configs/configschema"
@ -10,30 +12,27 @@ import (
"github.com/zclconf/go-cty/cty"
)
// ComposeMockValueBySchema composes mock value based on schema configuration. It uses
// configuration value as a baseline and populates null values with provided defaults.
// If the provided defaults doesn't contain needed fields, ComposeMockValueBySchema uses
// its own defaults. ComposeMockValueBySchema fails if schema contains dynamic types.
func ComposeMockValueBySchema(schema *configschema.Block, config cty.Value, defaults map[string]cty.Value) (
cty.Value, tfdiags.Diagnostics) {
return mockValueComposer{}.composeMockValueBySchema(schema, config, defaults)
// MockValueComposer provides different ways to generate mock values based on
// config schema, attributes, blocks and cty types in general.
type MockValueComposer struct {
rand *rand.Rand
}
type mockValueComposer struct {
getMockStringOverride func() string
}
func (mvc mockValueComposer) getMockString() string {
f := getRandomAlphaNumString
if mvc.getMockStringOverride != nil {
f = mvc.getMockStringOverride
func NewMockValueComposer(seed int64) MockValueComposer {
return MockValueComposer{
rand: rand.New(rand.NewSource(seed)), //nolint:gosec // It doesn't need to be secure.
}
return f()
}
func (mvc mockValueComposer) composeMockValueBySchema(schema *configschema.Block, config cty.Value, defaults map[string]cty.Value) (cty.Value, tfdiags.Diagnostics) {
// ComposeBySchema composes mock value based on schema configuration. It uses
// configuration value as a baseline and populates null values with provided defaults.
// If the provided defaults doesn't contain needed fields, ComposeBySchema uses
// its own defaults. ComposeBySchema fails if schema contains dynamic types.
// ComposeBySchema produces the same result with the given input values (seed and func arguments).
// It does so by traversing schema attributes, blocks and data structure elements / fields
// in a stable way by sorting keys or elements beforehand. Then, randomized values match
// between multiple ComposeBySchema calls, because seed and random sequences are the same.
func (mvc MockValueComposer) ComposeBySchema(schema *configschema.Block, config cty.Value, defaults map[string]cty.Value) (cty.Value, tfdiags.Diagnostics) {
var configMap map[string]cty.Value
var diags tfdiags.Diagnostics
@ -73,7 +72,7 @@ func (mvc mockValueComposer) composeMockValueBySchema(schema *configschema.Block
return cty.ObjectVal(mockValues), diags
}
func (mvc mockValueComposer) composeMockValueForAttributes(schema *configschema.Block, configMap map[string]cty.Value, defaults map[string]cty.Value) (map[string]cty.Value, tfdiags.Diagnostics) {
func (mvc MockValueComposer) composeMockValueForAttributes(schema *configschema.Block, configMap map[string]cty.Value, defaults map[string]cty.Value) (map[string]cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
addPotentialDefaultsWarning := func(key, description string) {
@ -90,7 +89,10 @@ func (mvc mockValueComposer) composeMockValueForAttributes(schema *configschema.
impliedTypes := schema.ImpliedType().AttributeTypes()
for k, attr := range schema.Attributes {
// Stable order is important here so random values match its fields between function calls.
for _, kv := range mapToSortedSlice(schema.Attributes) {
k, attr := kv.k, kv.v
// If the value present in configuration - just use it.
if cv, ok := configMap[k]; ok && !cv.IsNull() {
mockAttrs[k] = cv
@ -141,14 +143,17 @@ func (mvc mockValueComposer) composeMockValueForAttributes(schema *configschema.
return mockAttrs, diags
}
func (mvc mockValueComposer) composeMockValueForBlocks(schema *configschema.Block, configMap map[string]cty.Value, defaults map[string]cty.Value) (map[string]cty.Value, tfdiags.Diagnostics) {
func (mvc MockValueComposer) composeMockValueForBlocks(schema *configschema.Block, configMap map[string]cty.Value, defaults map[string]cty.Value) (map[string]cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
mockBlocks := make(map[string]cty.Value)
impliedTypes := schema.ImpliedType().AttributeTypes()
for k, block := range schema.BlockTypes {
// Stable order is important here so random values match its fields between function calls.
for _, kv := range mapToSortedSlice(schema.BlockTypes) {
k, block := kv.k, kv.v
// Checking if the config value really present for the block.
// It should be non-null and non-empty collection.
@ -213,12 +218,12 @@ func (mvc mockValueComposer) composeMockValueForBlocks(schema *configschema.Bloc
// to compose each value from the block's inner collection. It recursevily calls
// composeMockValueBySchema to proceed with all the inner attributes and blocks
// the same way so all the nested blocks follow the same logic.
func (mvc mockValueComposer) getMockValueForBlock(targetType cty.Type, configVal cty.Value, block *configschema.Block, defaults map[string]cty.Value) (cty.Value, tfdiags.Diagnostics) {
func (mvc MockValueComposer) getMockValueForBlock(targetType cty.Type, configVal cty.Value, block *configschema.Block, defaults map[string]cty.Value) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
switch {
case targetType.IsObjectType():
mockBlockVal, moreDiags := mvc.composeMockValueBySchema(block, configVal, defaults)
mockBlockVal, moreDiags := mvc.ComposeBySchema(block, configVal, defaults)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return cty.NilVal, diags
@ -231,10 +236,11 @@ func (mvc mockValueComposer) getMockValueForBlock(targetType cty.Type, configVal
var iterator = configVal.ElementIterator()
// Stable order is important here so random values match its fields between function calls.
for iterator.Next() {
_, blockConfigV := iterator.Element()
mockBlockVal, moreDiags := mvc.composeMockValueBySchema(block, blockConfigV, defaults)
mockBlockVal, moreDiags := mvc.ComposeBySchema(block, blockConfigV, defaults)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return cty.NilVal, diags
@ -254,10 +260,11 @@ func (mvc mockValueComposer) getMockValueForBlock(targetType cty.Type, configVal
var iterator = configVal.ElementIterator()
// Stable order is important here so random values match its fields between function calls.
for iterator.Next() {
blockConfigK, blockConfigV := iterator.Element()
mockBlockVal, moreDiags := mvc.composeMockValueBySchema(block, blockConfigV, defaults)
mockBlockVal, moreDiags := mvc.ComposeBySchema(block, blockConfigV, defaults)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return cty.NilVal, diags
@ -280,7 +287,7 @@ func (mvc mockValueComposer) getMockValueForBlock(targetType cty.Type, configVal
// getMockValueByType tries to generate mock cty.Value based on provided cty.Type.
// It will return non-ok response if it encounters dynamic type.
func (mvc mockValueComposer) getMockValueByType(t cty.Type) (cty.Value, bool) {
func (mvc MockValueComposer) getMockValueByType(t cty.Type) (cty.Value, bool) {
var v cty.Value
// just to be sure for cases when the logic below misses something
@ -309,8 +316,11 @@ func (mvc mockValueComposer) getMockValueByType(t cty.Type) (cty.Value, bool) {
case t.IsObjectType():
objVals := make(map[string]cty.Value)
// populate the object with mock values
for k, at := range t.AttributeTypes() {
// Populate the object with mock values. Stable order is important here
// so random values match its fields between function calls.
for _, kv := range mapToSortedSlice(t.AttributeTypes()) {
k, at := kv.k, kv.v
if t.AttributeOptional(k) {
continue
}
@ -335,19 +345,41 @@ func (mvc mockValueComposer) getMockValueByType(t cty.Type) (cty.Value, bool) {
return v, true
}
func getRandomAlphaNumString() string {
func (mvc MockValueComposer) getMockString() string {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
const minLength, maxLength = 4, 16
length := rand.Intn(maxLength-minLength) + minLength //nolint:gosec // It doesn't need to be secure.
length := mvc.rand.Intn(maxLength-minLength) + minLength
b := strings.Builder{}
b.Grow(length)
for i := 0; i < length; i++ {
b.WriteByte(chars[rand.Intn(len(chars))]) //nolint:gosec // It doesn't need to be secure.
b.WriteByte(chars[mvc.rand.Intn(len(chars))])
}
return b.String()
}
type keyValue[K cmp.Ordered, V any] struct {
k K
v V
}
// mapToSortedSlice makes it possible to iterate over map in a stable manner.
func mapToSortedSlice[K cmp.Ordered, V any](m map[K]V) []keyValue[K, V] {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
slices.Sort(keys)
s := make([]keyValue[K, V], 0, len(m))
for _, k := range keys {
s = append(s, keyValue[K, V]{k, m[k]})
}
return s
}

View File

@ -1,13 +1,15 @@
package hcl2shim
import (
"strings"
"testing"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/zclconf/go-cty/cty"
)
// TestComposeMockValueBySchema ensures different configschema.Block values
// processed correctly (lists, maps, objects, etc). Also, it should ensure that
// the resulting values are equal given the same set of inputs (seed, schema, etc).
func TestComposeMockValueBySchema(t *testing.T) {
t.Parallel()
@ -83,13 +85,13 @@ func TestComposeMockValueBySchema(t *testing.T) {
config: cty.NilVal,
wantVal: cty.ObjectVal(map[string]cty.Value{
"required-only": cty.NullVal(cty.String),
"required-computed": cty.StringVal("aaaaaaaa"),
"required-computed": cty.StringVal("xNmGyAVmNkB4"),
"optional": cty.NullVal(cty.String),
"optional-computed": cty.StringVal("aaaaaaaa"),
"computed-only": cty.StringVal("aaaaaaaa"),
"optional-computed": cty.StringVal("6zQu0"),
"computed-only": cty.StringVal("l3INvNSQT"),
"sensitive-optional": cty.NullVal(cty.String),
"sensitive-required": cty.NullVal(cty.String),
"sensitive-computed": cty.StringVal("aaaaaaaa"),
"sensitive-computed": cty.StringVal("ionwj3qrsh4xyC9"),
}),
},
"diff-props-in-single-block-attributes": {
@ -166,13 +168,13 @@ func TestComposeMockValueBySchema(t *testing.T) {
wantVal: cty.ObjectVal(map[string]cty.Value{
"nested": cty.ObjectVal(map[string]cty.Value{
"required-only": cty.NullVal(cty.String),
"required-computed": cty.StringVal("aaaaaaaa"),
"required-computed": cty.StringVal("xNmGyAVmNkB4"),
"optional": cty.NullVal(cty.String),
"optional-computed": cty.StringVal("aaaaaaaa"),
"computed-only": cty.StringVal("aaaaaaaa"),
"optional-computed": cty.StringVal("6zQu0"),
"computed-only": cty.StringVal("l3INvNSQT"),
"sensitive-optional": cty.NullVal(cty.String),
"sensitive-required": cty.NullVal(cty.String),
"sensitive-computed": cty.StringVal("aaaaaaaa"),
"sensitive-computed": cty.StringVal("ionwj3qrsh4xyC9"),
}),
}),
},
@ -208,10 +210,18 @@ func TestComposeMockValueBySchema(t *testing.T) {
Nesting: configschema.NestingList,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"field": {
"num": {
Type: cty.Number,
Computed: true,
},
"str1": {
Type: cty.String,
Computed: true,
},
"str2": {
Type: cty.String,
Computed: true,
},
},
},
},
@ -223,7 +233,9 @@ func TestComposeMockValueBySchema(t *testing.T) {
wantVal: cty.ObjectVal(map[string]cty.Value{
"nested": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"field": cty.NumberIntVal(0),
"num": cty.NumberIntVal(0),
"str1": cty.StringVal("l3INvNSQT"),
"str2": cty.StringVal("6zQu0"),
}),
}),
}),
@ -235,10 +247,18 @@ func TestComposeMockValueBySchema(t *testing.T) {
Nesting: configschema.NestingSet,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"field": {
"num": {
Type: cty.Number,
Computed: true,
},
"str1": {
Type: cty.String,
Computed: true,
},
"str2": {
Type: cty.String,
Computed: true,
},
},
},
},
@ -250,7 +270,9 @@ func TestComposeMockValueBySchema(t *testing.T) {
wantVal: cty.ObjectVal(map[string]cty.Value{
"nested": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"field": cty.NumberIntVal(0),
"num": cty.NumberIntVal(0),
"str1": cty.StringVal("l3INvNSQT"),
"str2": cty.StringVal("6zQu0"),
}),
}),
}),
@ -262,10 +284,18 @@ func TestComposeMockValueBySchema(t *testing.T) {
Nesting: configschema.NestingMap,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"field": {
"num": {
Type: cty.Number,
Computed: true,
},
"str1": {
Type: cty.String,
Computed: true,
},
"str2": {
Type: cty.String,
Computed: true,
},
},
},
},
@ -279,7 +309,9 @@ func TestComposeMockValueBySchema(t *testing.T) {
wantVal: cty.ObjectVal(map[string]cty.Value{
"nested": cty.MapVal(map[string]cty.Value{
"somelabel": cty.ObjectVal(map[string]cty.Value{
"field": cty.NumberIntVal(0),
"num": cty.NumberIntVal(0),
"str1": cty.StringVal("l3INvNSQT"),
"str2": cty.StringVal("6zQu0"),
}),
}),
}),
@ -304,8 +336,9 @@ func TestComposeMockValueBySchema(t *testing.T) {
},
"obj": {
Type: cty.Object(map[string]cty.Type{
"fieldNum": cty.Number,
"fieldStr": cty.String,
"fieldNum": cty.Number,
"fieldStr1": cty.String,
"fieldStr2": cty.String,
}),
Computed: true,
Optional: true,
@ -326,7 +359,12 @@ func TestComposeMockValueBySchema(t *testing.T) {
Computed: true,
Optional: true,
},
"str": {
"str1": {
Type: cty.String,
Computed: true,
Optional: true,
},
"str2": {
Type: cty.String,
Computed: true,
Optional: true,
@ -359,21 +397,23 @@ func TestComposeMockValueBySchema(t *testing.T) {
}),
wantVal: cty.ObjectVal(map[string]cty.Value{
"num": cty.NumberIntVal(0),
"str": cty.StringVal("aaaaaaaa"),
"str": cty.StringVal("xNmGyAVmNkB4"),
"bool": cty.False,
"obj": cty.ObjectVal(map[string]cty.Value{
"fieldNum": cty.NumberIntVal(0),
"fieldStr": cty.StringVal("aaaaaaaa"),
"fieldNum": cty.NumberIntVal(0),
"fieldStr1": cty.StringVal("l3INvNSQT"),
"fieldStr2": cty.StringVal("6zQu0"),
}),
"list": cty.ListValEmpty(cty.String),
"nested": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"num": cty.NumberIntVal(0),
"str": cty.StringVal("aaaaaaaa"),
"str1": cty.StringVal("mCp2gObD"),
"str2": cty.StringVal("iOtQNQsLiFD5"),
"bool": cty.False,
"obj": cty.ObjectVal(map[string]cty.Value{
"fieldNum": cty.NumberIntVal(0),
"fieldStr": cty.StringVal("aaaaaaaa"),
"fieldStr": cty.StringVal("ionwj3qrsh4xyC9"),
}),
"list": cty.ListValEmpty(cty.String),
}),
@ -443,12 +483,12 @@ func TestComposeMockValueBySchema(t *testing.T) {
wantVal: cty.ObjectVal(map[string]cty.Value{
"useConfigValue": cty.StringVal("iAmFromConfig"),
"useDefaultsValue": cty.StringVal("iAmFromDefaults"),
"generateMockValue": cty.StringVal("aaaaaaaa"),
"generateMockValue": cty.StringVal("l3INvNSQT"),
"nested": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"useConfigValue": cty.StringVal("iAmFromConfig"),
"useDefaultsValue": cty.StringVal("iAmFromDefaults"),
"generateMockValue": cty.StringVal("aaaaaaaa"),
"generateMockValue": cty.StringVal("6zQu0"),
}),
}),
}),
@ -456,21 +496,13 @@ func TestComposeMockValueBySchema(t *testing.T) {
},
}
const mockStringLength = 8
mvc := mockValueComposer{
getMockStringOverride: func() string {
return strings.Repeat("a", mockStringLength)
},
}
for name, test := range tests {
test := test
t.Run(name, func(t *testing.T) {
t.Parallel()
gotVal, gotDiags := mvc.composeMockValueBySchema(test.schema, test.config, test.defaults)
gotVal, gotDiags := NewMockValueComposer(42).ComposeBySchema(test.schema, test.config, test.defaults)
switch {
case test.wantError && !gotDiags.HasErrors():
t.Fatalf("Expected error in diags, but none returned")

View File

@ -68,6 +68,13 @@ type Module struct {
StaticEvaluator *StaticEvaluator
}
// GetProviderConfig uses name and alias to find the respective Provider configuration.
func (m *Module) GetProviderConfig(name, alias string) (*Provider, bool) {
tp := &Provider{Name: name, Alias: alias}
p, ok := m.ProviderConfigs[tp.moduleUniqueKey()]
return p, ok
}
// File describes the contents of a single configuration file.
//
// Individual files are not usually used alone, but rather combined together

View File

@ -37,6 +37,11 @@ type Provider struct {
// export this so providers don't need to be re-resolved.
// This same field is also added to the ProviderConfigRef struct.
providerType addrs.Provider
// IsMocked indicates if this provider has been mocked. It is used in
// testing framework to instantiate test provider wrapper.
IsMocked bool
MockResources []*MockResource
}
func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) {

View File

@ -71,6 +71,10 @@ type TestFile struct {
// Underlying modules shouldn't be called.
OverrideModules []*OverrideModule
// MockProviders is a map of providers that should be mocked. It is merged
// with Providers map to use later when instantiating provider instance.
MockProviders map[string]*MockProvider
VariablesDeclRange hcl.Range
}
@ -87,6 +91,30 @@ func (file *TestFile) Validate() tfdiags.Diagnostics {
return diags
}
func (file *TestFile) getTestProviderOrMock(addr string) (*Provider, bool) {
testProvider, ok := file.Providers[addr]
if ok {
return testProvider, true
}
mockProvider, ok := file.MockProviders[addr]
if ok {
p := &Provider{
Name: mockProvider.Name,
NameRange: mockProvider.NameRange,
Alias: mockProvider.Alias,
AliasRange: mockProvider.AliasRange,
DeclRange: mockProvider.DeclRange,
IsMocked: true,
MockResources: mockProvider.MockResources,
}
return p, true
}
return nil, false
}
// TestRun represents a single run block within a test file.
//
// Each run block represents a single OpenTofu command to be executed and a set
@ -254,9 +282,9 @@ func (r OverrideResource) getBlockName() string {
case addrs.DataResourceMode:
return blockNameOverrideData
case addrs.InvalidResourceMode:
return "invalid"
panic("BUG: invalid resource mode in override resource")
default:
return "invalid"
panic("BUG: undefined resource mode in override resource: " + r.Mode.String())
}
}
@ -273,6 +301,60 @@ type OverrideModule struct {
Outputs map[string]cty.Value
}
const blockNameMockProvider = "mock_provider"
// MockProvider represents mocked provider block. It partially matches
// the Provider configuration block (name, alias) and includes additional
// mocking data (mock resources).
type MockProvider struct {
// Fields below are copied from configs.Provider struct:
Name string
NameRange hcl.Range
Alias string
AliasRange *hcl.Range // nil if no alias set
DeclRange hcl.Range
// Fields below are specific to configs.MockProvider:
MockResources []*MockResource
}
// moduleUniqueKey is copied from Provider.moduleUniqueKey
func (p *MockProvider) moduleUniqueKey() string {
if p.Alias != "" {
return fmt.Sprintf("%s.%s", p.Name, p.Alias)
}
return p.Name
}
const (
blockNameMockResource = "mock_resource"
blockNameMockData = "mock_data"
)
// MockResource represents mocked resource. It is similar to OverrideResource,
// except all the resources with the same type should be overridden (mocked).
type MockResource struct {
Mode addrs.ResourceMode
Type string
Defaults map[string]cty.Value
}
func (r MockResource) getBlockName() string {
switch r.Mode {
case addrs.ManagedResourceMode:
return blockNameMockResource
case addrs.DataResourceMode:
return blockNameMockData
case addrs.InvalidResourceMode:
panic("BUG: invalid resource mode in mock resource")
default:
panic("BUG: undefined resource mode in mock resource: " + r.Mode.String())
}
}
func loadTestFile(body hcl.Body) (*TestFile, hcl.Diagnostics) {
var diags hcl.Diagnostics
@ -280,7 +362,8 @@ func loadTestFile(body hcl.Body) (*TestFile, hcl.Diagnostics) {
diags = append(diags, contentDiags...)
tf := TestFile{
Providers: make(map[string]*Provider),
Providers: make(map[string]*Provider),
MockProviders: make(map[string]*MockProvider),
}
for _, block := range content.Blocks {
@ -340,6 +423,24 @@ func loadTestFile(body hcl.Body) (*TestFile, hcl.Diagnostics) {
tf.OverrideModules = append(tf.OverrideModules, overrideMod)
}
case blockNameMockProvider:
mockProvider, mockProviderDiags := decodeMockProviderBlock(block)
diags = append(diags, mockProviderDiags...)
if !mockProviderDiags.HasErrors() {
k := mockProvider.moduleUniqueKey()
if _, ok := tf.MockProviders[k]; ok {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicated `mock_provider` block",
Detail: fmt.Sprintf("It is not allowed to have multiple `mock_provider` blocks with the same address: `%v`.", k),
Subject: mockProvider.DeclRange.Ptr(),
})
} else {
tf.MockProviders[k] = mockProvider
}
}
}
}
@ -734,6 +835,108 @@ func decodeOverrideModuleBlock(block *hcl.Block) (*OverrideModule, hcl.Diagnosti
return mod, diags
}
// Some code of decodeMockProviderBlock function was copied from decodeProviderBlock.
func decodeMockProviderBlock(block *hcl.Block) (*MockProvider, hcl.Diagnostics) {
var diags hcl.Diagnostics
content, moreDiags := block.Body.Content(mockProviderBlockSchema)
diags = append(diags, moreDiags...)
// Provider names must be localized. Produce an error with a message
// indicating the action the user can take to fix this message if the local
// name is not localized.
name := block.Labels[0]
nameDiags := checkProviderNameNormalized(name, block.DefRange)
diags = append(diags, nameDiags...)
if nameDiags.HasErrors() {
// If the name is invalid then we mustn't produce a result because
// downstreams could try to use it as a provider type and then crash.
return nil, diags
}
provider := &MockProvider{
Name: name,
NameRange: block.LabelRanges[0],
DeclRange: block.DefRange,
}
if attr, exists := content.Attributes["alias"]; exists {
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &provider.Alias)
diags = append(diags, valDiags...)
provider.AliasRange = attr.Expr.Range().Ptr()
if !hclsyntax.ValidIdentifier(provider.Alias) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid mock provider configuration alias",
Detail: fmt.Sprintf("An alias must be a valid name. %s", badIdentifierDetail),
Subject: provider.AliasRange,
})
}
}
var (
managedResources = make(map[string]struct{})
dataResources = make(map[string]struct{})
)
for _, block := range content.Blocks {
res, resDiags := decodeMockResourceBlock(block)
diags = append(diags, resDiags...)
if resDiags.HasErrors() {
continue
}
resources := managedResources
if res.Mode == addrs.DataResourceMode {
resources = dataResources
}
if _, ok := resources[res.Type]; ok {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Duplicated `%v` block", res.getBlockName()),
Detail: fmt.Sprintf("`%v.%v` is already defined in `mock_provider` block.", res.getBlockName(), res.Type),
Subject: provider.DeclRange.Ptr(),
})
continue
}
resources[res.Type] = struct{}{}
provider.MockResources = append(provider.MockResources, res)
}
return provider, diags
}
func decodeMockResourceBlock(block *hcl.Block) (*MockResource, hcl.Diagnostics) {
var mode addrs.ResourceMode
switch block.Type {
case blockNameMockResource:
mode = addrs.ManagedResourceMode
case blockNameMockData:
mode = addrs.DataResourceMode
default:
panic("BUG: unsupported block type for mock resource: " + block.Type)
}
res := &MockResource{
Mode: mode,
Type: block.Labels[0],
}
content, diags := block.Body.Content(mockResourceBlockSchema)
if attr, exists := content.Attributes["defaults"]; exists {
v, moreDiags := parseObjectAttrWithNoVariables(attr)
res.Defaults, diags = v, append(diags, moreDiags...)
}
return res, diags
}
func parseObjectAttrWithNoVariables(attr *hcl.Attribute) (map[string]cty.Value, hcl.Diagnostics) {
attrVal, valDiags := attr.Expr.Value(nil)
diags := valDiags
@ -821,6 +1024,10 @@ var testFileSchema = &hcl.BodySchema{
{
Type: blockNameOverrideModule,
},
{
Type: blockNameMockProvider,
LabelNames: []string{"name"},
},
},
}
@ -898,3 +1105,32 @@ var overrideModuleBlockSchema = &hcl.BodySchema{
},
},
}
//nolint:gochecknoglobals // To follow existing code style.
var mockProviderBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "alias",
Required: false,
},
},
Blocks: []hcl.BlockHeaderSchema{
{
Type: blockNameMockResource,
LabelNames: []string{"type"},
},
{
Type: blockNameMockData,
LabelNames: []string{"type"},
},
},
}
//nolint:gochecknoglobals // To follow existing code style.
var mockResourceBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "defaults",
},
},
}

View File

@ -145,6 +145,18 @@ func (ctx *BuiltinEvalContext) InitProvider(addr addrs.AbsProviderConfig) (provi
return nil, err
}
if ctx.Evaluator != nil && ctx.Evaluator.Config != nil && ctx.Evaluator.Config.Module != nil {
// If an aliased provider is mocked, we use providerForTest wrapper.
// We cannot wrap providers.Factory itself, because factories don't support aliases.
pc, ok := ctx.Evaluator.Config.Module.GetProviderConfig(addr.Provider.Type, addr.Alias)
if ok && pc.IsMocked {
p, err = newProviderForTest(p, pc.MockResources)
if err != nil {
return nil, err
}
}
}
log.Printf("[TRACE] BuiltinEvalContext: Initialized %q provider for %s", addr.String(), addr)
ctx.ProviderCache[key] = p

View File

@ -681,7 +681,7 @@ func (n *NodeAbstractResourceInstance) plan(
var keyData instances.RepetitionData
resource := n.Addr.Resource.Resource
provider, providerSchema, err := n.getProviderWithPlannedChange(ctx, n.ResolvedProvider, plannedChange)
provider, providerSchema, err := n.getProvider(ctx, n.ResolvedProvider)
if err != nil {
return nil, nil, keyData, diags.Append(err)
}
@ -2573,10 +2573,6 @@ func resourceInstancePrevRunAddr(ctx EvalContext, currentAddr addrs.AbsResourceI
}
func (n *NodeAbstractResourceInstance) getProvider(ctx EvalContext, addr addrs.AbsProviderConfig) (providers.Interface, providers.ProviderSchema, error) {
return n.getProviderWithPlannedChange(ctx, addr, nil)
}
func (n *NodeAbstractResourceInstance) getProviderWithPlannedChange(ctx EvalContext, addr addrs.AbsProviderConfig, plannedChange *plans.ResourceInstanceChange) (providers.Interface, providers.ProviderSchema, error) {
underlyingProvider, schema, err := getProvider(ctx, addr)
if err != nil {
return nil, providers.ProviderSchema{}, err
@ -2586,15 +2582,9 @@ func (n *NodeAbstractResourceInstance) getProviderWithPlannedChange(ctx EvalCont
return underlyingProvider, schema, nil
}
providerForTest := providerForTest{
internal: underlyingProvider,
schema: schema,
overrideValues: n.Config.OverrideValues,
}
providerForTest := newProviderForTestWithSchema(underlyingProvider, schema)
if plannedChange != nil {
providerForTest.plannedChange = &plannedChange.After
}
providerForTest.setSingleResource(n.Addr.Resource.Resource, n.Config.OverrideValues)
return providerForTest, schema, nil
}

View File

@ -6,126 +6,202 @@
package tofu
import (
"fmt"
"hash/fnv"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/hcl2shim"
"github.com/opentofu/opentofu/internal/providers"
"github.com/zclconf/go-cty/cty"
)
var _ providers.Interface = providerForTest{}
var _ providers.Interface = &providerForTest{}
// providerForTest is a wrapper around real provider to allow certain resources to be overridden
// for testing framework. Currently, it's used in NodeAbstractResourceInstance only in a format
// of one time use. It handles overrideValues and plannedChange for a single resource instance
// (i.e. by certain address).
// TODO: providerForTest should be extended to handle mock providers implementation with per-type
// mocking. It will allow providerForTest to be used for both overrides and full mocking.
// In such scenario, overrideValues should be extended to handle per-type values and plannedChange
// should contain per PlanResourceChangeRequest cache to produce the same plan result
// for the same PlanResourceChangeRequest.
// providerForTest is a wrapper around a real provider to allow certain resources to be overridden
// (by address) or mocked (by provider and resource type) for testing framework.
type providerForTest struct {
// It's not embedded to make it safer to extend providers.Interface
// without silently breaking providerForTest functionality.
// providers.Interface is not embedded to make it safer to extend
// the interface without silently breaking providerForTest functionality.
internal providers.Interface
schema providers.ProviderSchema
overrideValues map[string]cty.Value
plannedChange *cty.Value
managedResources resourceForTestByType
dataResources resourceForTestByType
}
func (p providerForTest) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse {
func newProviderForTestWithSchema(internal providers.Interface, schema providers.ProviderSchema) *providerForTest {
return &providerForTest{
internal: internal,
schema: schema,
managedResources: make(resourceForTestByType),
dataResources: make(resourceForTestByType),
}
}
func newProviderForTest(internal providers.Interface, res []*configs.MockResource) (providers.Interface, error) {
schema := internal.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
return nil, fmt.Errorf("getting provider schema for test wrapper: %w", schema.Diagnostics.Err())
}
p := newProviderForTestWithSchema(internal, schema)
p.addMockResources(res)
return p, nil
}
func (p *providerForTest) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse {
var resp providers.ReadResourceResponse
resSchema, _ := p.schema.SchemaForResourceType(addrs.ManagedResourceMode, r.TypeName)
resp.NewState, resp.Diagnostics = hcl2shim.ComposeMockValueBySchema(resSchema, r.ProviderMeta, p.overrideValues)
overrideValues := p.managedResources.getOverrideValues(r.TypeName)
resp.NewState, resp.Diagnostics = newMockValueComposer(r.TypeName).
ComposeBySchema(resSchema, r.ProviderMeta, overrideValues)
return resp
}
func (p providerForTest) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
func (p *providerForTest) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
if r.Config.IsNull() {
return providers.PlanResourceChangeResponse{
PlannedState: r.ProposedNewState, // null
}
}
if p.plannedChange != nil {
return providers.PlanResourceChangeResponse{
PlannedState: *p.plannedChange,
}
}
resSchema, _ := p.schema.SchemaForResourceType(addrs.ManagedResourceMode, r.TypeName)
overrideValues := p.managedResources.getOverrideValues(r.TypeName)
var resp providers.PlanResourceChangeResponse
resp.PlannedState, resp.Diagnostics = hcl2shim.ComposeMockValueBySchema(resSchema, r.Config, p.overrideValues)
resp.PlannedState, resp.Diagnostics = newMockValueComposer(r.TypeName).
ComposeBySchema(resSchema, r.Config, overrideValues)
return resp
}
func (p providerForTest) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
func (p *providerForTest) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
return providers.ApplyResourceChangeResponse{
NewState: r.PlannedState,
}
}
func (p providerForTest) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
func (p *providerForTest) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
resSchema, _ := p.schema.SchemaForResourceType(addrs.DataResourceMode, r.TypeName)
var resp providers.ReadDataSourceResponse
resp.State, resp.Diagnostics = hcl2shim.ComposeMockValueBySchema(resSchema, r.Config, p.overrideValues)
overrideValues := p.dataResources.getOverrideValues(r.TypeName)
resp.State, resp.Diagnostics = newMockValueComposer(r.TypeName).
ComposeBySchema(resSchema, r.Config, overrideValues)
return resp
}
// Calling the internal provider ensures providerForTest has the same behaviour as if
// it wasn't overridden. Some of these functions should be changed in the future to
// support mock_provider (e.g. ConfigureProvider should do nothing), mock_resource and
// mock_data. The only exception is ImportResourceState, which panics if called via providerForTest
// because importing is not supported in testing framework.
// it wasn't overridden or mocked. The only exception is ImportResourceState, which panics
// if called via providerForTest because importing is not supported in testing framework.
func (p providerForTest) GetProviderSchema() providers.GetProviderSchemaResponse {
func (p *providerForTest) GetProviderSchema() providers.GetProviderSchemaResponse {
return p.internal.GetProviderSchema()
}
func (p providerForTest) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
func (p *providerForTest) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
return p.internal.ValidateProviderConfig(r)
}
func (p providerForTest) ValidateResourceConfig(r providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
func (p *providerForTest) ValidateResourceConfig(r providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
return p.internal.ValidateResourceConfig(r)
}
func (p providerForTest) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse {
func (p *providerForTest) ValidateDataResourceConfig(r providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse {
return p.internal.ValidateDataResourceConfig(r)
}
func (p providerForTest) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
func (p *providerForTest) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
return p.internal.UpgradeResourceState(r)
}
func (p providerForTest) ConfigureProvider(r providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
return p.internal.ConfigureProvider(r)
// providerForTest doesn't configure its internal provider because it is mocked.
func (p *providerForTest) ConfigureProvider(_ providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
return providers.ConfigureProviderResponse{}
}
func (p providerForTest) Stop() error {
func (p *providerForTest) Stop() error {
return p.internal.Stop()
}
func (p providerForTest) GetFunctions() providers.GetFunctionsResponse {
func (p *providerForTest) GetFunctions() providers.GetFunctionsResponse {
return p.internal.GetFunctions()
}
func (p providerForTest) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse {
func (p *providerForTest) CallFunction(r providers.CallFunctionRequest) providers.CallFunctionResponse {
return p.internal.CallFunction(r)
}
func (p providerForTest) Close() error {
func (p *providerForTest) Close() error {
return p.internal.Close()
}
func (p providerForTest) ImportResourceState(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
func (p *providerForTest) ImportResourceState(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
panic("Importing is not supported in testing context. providerForTest must not be used to call ImportResourceState")
}
func (p *providerForTest) setSingleResource(addr addrs.Resource, overrides map[string]cty.Value) {
res := resourceForTest{
overrideValues: overrides,
}
switch addr.Mode {
case addrs.ManagedResourceMode:
p.managedResources[addr.Type] = res
case addrs.DataResourceMode:
p.dataResources[addr.Type] = res
case addrs.InvalidResourceMode:
panic("BUG: invalid mock resource mode")
default:
panic("BUG: unsupported resource mode: " + addr.Mode.String())
}
}
func (p *providerForTest) addMockResources(mockResources []*configs.MockResource) {
for _, mockRes := range mockResources {
var resources resourceForTestByType
switch mockRes.Mode {
case addrs.ManagedResourceMode:
resources = p.managedResources
case addrs.DataResourceMode:
resources = p.dataResources
case addrs.InvalidResourceMode:
panic("BUG: invalid mock resource mode")
default:
panic("BUG: unsupported mock resource mode: " + mockRes.Mode.String())
}
resources[mockRes.Type] = resourceForTest{
overrideValues: mockRes.Defaults,
}
}
}
type resourceForTest struct {
overrideValues map[string]cty.Value
}
type resourceForTestByType map[string]resourceForTest
func (m resourceForTestByType) getOverrideValues(typeName string) map[string]cty.Value {
return m[typeName].overrideValues
}
func newMockValueComposer(typeName string) hcl2shim.MockValueComposer {
hash := fnv.New64()
hash.Write([]byte(typeName))
return hcl2shim.NewMockValueComposer(int64(hash.Sum64()))
}

View File

@ -0,0 +1,11 @@
data "local_file" "bucket_name" {
filename = "bucket_name.txt"
}
provider "aws" {
region = "us-east-2"
}
resource "aws_s3_bucket" "test" {
bucket = data.local_file.bucket_name.content
}

View File

@ -0,0 +1,29 @@
// All resources and data sources provided by `aws.mock` provider
// will be mocked. Their values will be automatically generated.
mock_provider "aws" {
alias = "mock"
}
// The same goes for `local` provider. Also, every `local_file`
// data source will have its `content` set to `test`.
mock_provider "local" {
mock_data "local_file" {
defaults = {
content = "test"
}
}
}
// Test if the bucket name is correctly passed to the aws_s3_bucket
// resource from the local file.
run "test" {
// Use `aws.mock` provider for this test run only.
providers = {
aws = aws.mock
}
assert {
condition = aws_s3_bucket.test.bucket == "test"
error_message = "Incorrect bucket name: ${aws_s3_bucket.test.bucket}"
}
}

View File

@ -29,6 +29,8 @@ import ExpectFailureResourcesMain from '!!raw-loader!./examples/expect_failures_
import ExpectFailureResourcesTest from '!!raw-loader!./examples/expect_failures_resources/main.tftest.hcl'
import OverrideResourceMain from '!!raw-loader!./examples/override_resource/main.tf'
import OverrideResourceTest from '!!raw-loader!./examples/override_resource/main.tftest.hcl'
import MockProviderMain from '!!raw-loader!./examples/mock_provider/main.tf'
import MockProviderTest from '!!raw-loader!./examples/mock_provider/main.tftest.hcl'
import OverrideModuleMain from '!!raw-loader!./examples/override_module/main.tf'
import OverrideModuleTest from '!!raw-loader!./examples/override_module/main.tftest.hcl'
import OverrideModuleBucketMeta from '!!raw-loader!./examples/override_module/bucket_meta/main.tf'
@ -132,9 +134,10 @@ A test file consists of:
* A **[`variables` block](#the-variables-and-runvariables-blocks)** (optional): define variables for all tests in the
current file.
* The **[`provider` blocks](#the-providers-block)** (optional): define the providers to be used for the tests.
* The **[`override_resource` block](#the-override_resource-and-override_data-blocks)** (optional): defines a resource to be overridden.
* The **[`override_data` block](#the-override_resource-and-override_data-blocks)** (optional): defines a data source to be overridden.
* The **[`override_module` block](#the-override_module-block)** (optional): defines a module call to be overridden.
* The **[`mock_provider` blocks](#the-mock-providers-blocks)** (optional): define the providers to be mocked.
* The **[`override_resource` blocks](#the-override_resource-and-override_data-blocks)** (optional): define the resources to be overridden.
* The **[`override_data` blocks](#the-override_resource-and-override_data-blocks)** (optional): define the data sources to be overridden.
* The **[`override_module` blocks](#the-override_module-block)** (optional): define the module calls to be overridden.
### The `run` block
@ -386,12 +389,46 @@ of the file.
</TabItem>
</Tabs>
### The `mock_provider` blocks
A `mock_provider` block allows you to replace provider configuration by a mocked one. In such scenario,
creation and retrieval of provider resources and data sources will be skipped. Instead, OpenTofu
will automatically generate all computed attributes and blocks to be used in tests.
:::tip Note
Learn more on how OpenTofu produces [automatically generated values](#automatically-generated-values).
:::
Mock providers also support `alias` field as well as `mock_resource` and `mock_data` blocks.
In some cases, you may want to use default values instead of automatically generated ones by passing them
inside `defaults` field of `mock_resource` or `mock_data` blocks.
In the example below, we test if the bucket name is correctly passed to the resource
without actually creating it:
<Tabs>
<TabItem value={"test"} label={"main.tftest.hcl"} default>
<CodeBlock language={"hcl"}>{MockProviderTest}</CodeBlock>
</TabItem>
<TabItem value={"main"} label={"main.tf"}>
<CodeBlock language={"hcl"}>{MockProviderMain}</CodeBlock>
</TabItem>
</Tabs>
### The `override_resource` and `override_data` blocks
In some cases you may want to test your infrastructure with certain resources or data sources being overridden.
You can use the `override_resource` or `override_data` blocks to skip creation and retrieval of these resources or data sources using the real provider.
Instead, OpenTofu will automatically generate all computed attributes and blocks to be used in tests.
:::tip Note
Learn more on how OpenTofu produces [automatically generated values](#automatically-generated-values).
:::
These blocks consist of the following elements:
| Name | Type | Description |
@ -419,9 +456,9 @@ Each instance of a resource or data source must be overridden.
:::
#### Automatically generated values
### Automatically generated values
Overriding resources and data sources requires OpenTofu to automatically generate computed attributes without calling respective providers.
Mocking resources and data sources requires OpenTofu to automatically generate computed attributes without calling respective providers.
When generating these values, OpenTofu cannot follow custom provider logic, so it uses simple rules based on value type:
| Attribute type | Generated value |
@ -437,7 +474,7 @@ When generating these values, OpenTofu cannot follow custom provider logic, so i
:::tip Note
You can set custom values to use instead of automatically generated ones via `values` field in both `override_resource` and `override_data` blocks.
You can set custom values to use instead of automatically generated ones via respective mock or override fields.
Keep in mind, it's only possible for computed attributes and configuration values cannot be changed.
:::