Front End Testing

Components Architecture

Flux (Unidirectional Data Flow Architecture)

Presentational Components

  • Are concerned with how things look.
  • May contain both presentational and container components** inside, and usually have some DOM markup and styles of their own.
  • Often allow containment via this.props.children.
  • Have no dependencies on the rest of the app, such as Flux actions or stores.
  • Don’t specify how the data is loaded or mutated.
  • Receive data and callbacks exclusively via props.
  • Rarely have their own state (when they do, it’s UI state rather than data).

PokemonCard

<template>
  <div
    class="flex flex-col mx-6 bg-blue rounded-lg py-4 px-8 w-32 h-32 my-6 cursor-pointer"
    tid="pokemon-card"
  >
    <img class="w-16 h-16 rounded" :src="pokemon.url"/>
    <p class="text-lg text-center">{{ pokemon.name }}</p>
  </div>
</template>

<script>
export default {
  name: 'PokemonCard',
  props: ['pokemon'],
};
</script>

Concerns

  • Receives props and renders well
  • It doesn't care about who is using it.
  • Doesn't know anything about Flux or Stores or Local Storage or whatever. It's full decoupled from application logic

Container Components

  • Are concerned with how things work.
  • May contain both presentational and container components inside but usually don’t have any DOM markup of their own except for some wrapping divs, and never have any styles.
  • Provide the data and behavior to presentational or other container components.
  • Call Flux actions and provide these as callbacks to the presentational components.
  • Are often stateful, as they tend to serve as data sources.

PokemonList

<template>
  <div class="container flex flex-row flex-wrap">
    <template v-for="pokemon in pokemons">
      <pokemon-card
        :key="pokemon.name"
        :pokemon="pokemon"
      />
    </template>
  </div>
</template>

<script>
import { mapActions, mapState } from 'vuex';
import PokemonCard from '@/components/PokemonCard.vue';
import store from '@/store';

export default {
  name: 'PokeList',
  components: { PokemonCard },
  store,
  computed: { ...mapState({ pokemons: (state) => state.pokemons }) },
  methods: { ...mapActions(['getPokemons']) },
  mounted: function () { return this.getPokemons() }
};
</script>

Concerns

  • Knows about the structure of the Flux Store.
  • Orchestrate Presentational Components 
  • Executes application logic actions like fetching data

Decompose

Presentationals

Search Input

AmazonLogo

ShoppingCart

Product Card

Filters

OrderingDropdown

Containers

Header

Product Results

Pyramid of Testing

Unit Testing

Tests oriented towards checking the functionality of individual pieces of a software by verifying the results from that module are deterministic to a defined input. 

Unit Tests !== Integration Tests

class Calculator
  def sum(a, b)
    a + b
  end

  def sub(a, b)
    a - b
  end
end
class TestCalculator
  def test_sum
    assert_equal calculator.sum(2, 2), 4
    assert_equal calculator.sum(3, 5), 8
  end

  def test_substraction
    assert_equal calculator.sub(4, 2), 2
    assert_equal calculator.sub(10, 10), 0
    assert_equal calculator.sub(10, 20), -10
  end
end

Production Code

Testing Code

Complex Unit Testing / External Dependencies

class MailgunSender:
  def initialize(api_key):
    @api_key = api_key
    @client = MailgunClient.new @api_key
  end

  def send(email):
    response = @client.send build_payload(email)
    
    if has_error? response
      raise MailgunUnauthorized if unauthorized? response
      raise MailgunBadFormat if bad_format? response

      raise MailgunError
    end
  end
end

Unit test should test small unit of code that could not be influenced by external modules at the moment of testing (libraries, HTTP Request, etc). This is required for tests to be Deterministic

Testing

Example

 

class MailgunSenderTest
  def test_sends_email
    # Mocking phase
    MailgunClient
      .stubs(:send)
      .returns(GOOD_RESPONSE)

    mailgun_sender = MailgunSender.new 'fake-api-key'

    assert_not_raises MailgunError do
      mailgun_sender.send(EMAIL)
    end
  end

  def test_unauthorized
    MailgunClient
      .stubs(:send)
      .returns(UNAUTHORIZED_RESPONSE)
 
    mailgun_sender = MailgunSender.new 'fake-api-key'
    
    assert_raises MailgunUnauthorized do
      mailgun_sender.send(EMAIL)
    end
  end

  def test_bad_request
    MailgunClient
      .stubs(:send)
      .returns(BAD_REQUEST_RESPONSE)

    mailgun_sender = MailgunSender.new 'fake-api-key'
    
    assert_raises MailgunBadRequest do
      mailgun_sender.send(EMAIL)
    end
  end
end

Integration Tests

Tests oriented towards checking the functionality of compound pieces of software by verifying the results from the composition are deterministic to a defined input, taking into consideration external modules also.

The whole picture

Workshop time

Front End Testing

By Alberto Romero

Front End Testing

  • 210