Context Attributes and Functions

Other than Context Hooks, you can also configure contexts with any attributes or functions.

Attributes

You can set any arbitrary attribute from within any hook:

@context.before
def before(self):
  self.calculator = Calculator()

and refer it later on:

@context.example
def is_a_calculaor(self):
  assert type(self.calculator) == Calculator

Memoized Attributes

Memoized attributes allow for lazy construction of attributes needed during a test. The attribute value will be constructed and remembered only at the first attribute access:

@context
def Memoized_attributes(context):

  # This function will be used to lazily set a memoized attribute with the same name
  @context.memoize
  def memoized_value(self):
    return []

  # Lambdas are also OK
  context.memoize('another_memoized_value', lambda self: [])

  # Or in bulk
  context.memoize(
    yet_another=lambda self: 'one',
    and_one_more=lambda self: 'attr',
  )

  @context.example
  def can_access_memoized_attributes(self):
    # memoized_value
    assert len(self.memoized_value) == 0
    self.memoized_value.append(True)
    assert len(self.memoized_value) == 1

    # another_memoized_value
    assert len(self.another_memoized_value) == 0
    self.another_memoized_value.append(True)
    assert len(self.another_memoized_value) == 1

    # these were declared in bulk
    assert self.yet_anoter == 'one'
    assert self.and_one_more == 'attr'

Note in the example that the list built by memoized_value(), is memoized, and is the same object for every access.

Another option is to force memoization to happen at a before hook, instead of at the moment the attribute is accessed:

@context.memoize_before
def attribute_name(self):
  return []

In this case, the attribute will be set, regardless if it is used or not.

Composition

The big value of using memoized attributes as opposed to a regular attribute, is that you can easily do composition:

from testslide.dsl import context
from testslide import StrictMock

@context
def Composition(context):

  context.memoize('attr_value', lambda self: 'default value')

  @context.memoize
  def mock(self):
    mock = StrictMock()
    mock.attr = self.attr_value
    return mock

  @context.example
  def sees_default_value(self):
    self.assertEqual(self.mock.attr, 'default value')

  @context.sub_context
  def With_different_value(context):

    context.memoize('attr_value', lambda self: 'different value')

    @context.example
    def sees_different_value(self):
      self.assertEqual(self.mock.attr, 'different value')

Functions

You can define arbitrary functions that can be called from test code with the @context.function decorator:

@context
def Arbitrary_helper_functions(context):

  @context.memoize
  def some_list(self):
    return []

  # You can define arbitrary functions to call later
  @context.function
  def my_helper_function(self):
    self.some_list.append('item')
    return "I'm helping!"

  @context.example
  def can_call_helper_function(self):
    assert "I'm helping!" == self.my_helper_function()
    assert ['item'] == self.some_list