DEV Community

loading...

The What, Why and How of React (Testing with Hooks)

mangel0111 profile image miguel-penaloza ・20 min read

This's the second part of the series of post focus on testing a React app if you want to check the first one where we talk about basic concepts you can find it here.

Today we're going to talk about testing in a deeper way. I will explain how to test a react application, best pattern to make tests, nice-to-have utilities, and some tips to make the TDD/BDD technique easy while you code your application to make your tests more SOLID.

NOTES: In this post, I will replace enzyme with 'react-testing-library', that in my humble opinion is more restricted than enzyme but at the same time force you to make better tests.

What else should I learn about testing?

In the previous post, we talk about what is testing, basic concepts and some of the libraries we can use, but is just the tip of the iceberg, the culture of testing is not just something you just learn acronyms and then you can apply it on your code, is not a separate part of the development.

One thing that happens since my last post, was the release of the React v16.8. When they introduce the hooks concept in our lives, I see a lot of post of people trying to explain why you should migrate to this new thing. They explain you with examples how can you replace each internal method of the lifecycle (componentDidMount, etc) with a hook (useEffect, useState, etc), but when I tried to find out people testing hooks, there's not so much information out there.

In my projects, I try to migrate to the latest version of react then I will be able to use the hooks, because of the promise of a more functional world when our React components become in just a thing that receives data and actions, where his only responsibility is to make the call to this action and display his data, and for me is something very easy to buy.

When I tried to use my first hook, I trust on my code and my tests, I trust that my tests will report the fail when something breaks, I expect that my tests fail if something is not ok on the migration of a Class component to a Components with hooks, my test should not break. The UI should keep same, the data received is the same, and the same actions should be called, I'm just moving implementations details inside my component.

In my team, we use to have the rule of just create a Class React Component if you need it, and the rule works for us, we only create Class Components when we need to handle states, or do something on the lifecycles (mount, update or unmount) otherwise we use a function that returns the component, is a normal rule that I understand many people follow.

When I try to migrate my first Class Component was easy because only use state, I just need to replace the state of the class component with an useState my class looks like this, is just a silly Input that animates the title when you focus the input, nothing of business logic, nothing too complicated to deal.

export class Input extends Component {
    constructor(props){
        super(props);
        this.state = {
            focus: false
        };
    }
    render(){
        const { title, value, isFilter } = this.props;
        return(
            <div>
                {title && <LabelTitle value={value} isFocus={this.state.focus}>{title}</LabelTitle>}
                <InputForm 
                    onFocus={()=> this.setState({ focus: true })}
                    onBlur={()=> this.setState({ focus: false })}
                    {...this.props}
                />
                {isFilter && <IconInput><img src={iconEye} alt="icon-eye" /></IconInput> }
            </div>);

    }
}

When I migrate my component, now looks like this:

export const Input = ({ title, value, isFilter, type, width, onChange }) => {
    const [focus, changeFocus] = useState(false);
    return (
        <div>
            {title && <LabelTitle value={value} isFocus={focus}>{title}</LabelTitle>}
            <InputForm
                onFocus={() => changeFocus(true)}
                onBlur={() => changeFocus(false)}
                type={type}
                width={width}
                onChange={onChange}
            />
            {isFilter && <IconInput><img src={iconEye} alt="icon-eye" /></IconInput>}
        </div>);
};

Is essentially the same component, the same behavior with less code, but my tests were in red, all the unit test related to the input behavior fails, when I try to understand the why I realize that my test was verifying this in one of his assertions:

expect(input.state('focus')).toBeFalsy();

I realize that now I don't have a .state function, because is not a class, is just a component, then I also realize that I overuse the .update() and the setProps() on my previous tests, my tests were ok when I coded, but now my test are connected to my implementation, if I try to migrate to the latest version of React my test are going to fail, that means that I need to refactor all my tests and my code to use the hooks.

I was in a crossroads, I can let the code as it is, is working, no one is asking me to migrate to hooks, I don't need to refactor everything just to use something new, but I realize something bigger than hooks on my code, my tests are blocking me to make good code, that's why I choose to refactor everything to make the code great again.

But before to think in refactors I need to understand why my tests are so bound to the details of the implementations, I check my tests over and over and I found tests where I use mount and shallow of enzyme to render components and then check by state and props. I also use to update props with setProps to simulate data received, that was ok at the moment but now react changes (with backward compatibility) I can't upgrade because I marry my code with his tests.

After a long time of research, I found a new library to help me with my tests called React Testing Library, I checked and this library gives you fewer things that enzyme, you can't check states, props or manipulate lifecycles, you only can render once pass props, find by testid and wait for elements to be displayed, check this:

test('Fetch makes an API call and displays the greeting when load-greeting is clicked', async () => {
  // Arrange
  axiosMock.get.mockResolvedValueOnce({data: {greeting: 'hello there'}})
  const url = '/greeting'
  const {getByText, getByTestId, container, asFragment} = render(
    <Fetch url={url} />,
  )

  // Act
  fireEvent.click(getByText(/load greeting/i))

  // Let's wait until our mocked `get` request promise resolves and
  // the component calls setState and re-renders.
  // getByTestId throws an error if it cannot find an element with the given ID
  // and waitForElement will wait until the callback doesn't throw an error
  const greetingTextNode = await waitForElement(() =>
    getByTestId('greeting-text'),
  )

  // Assert
  expect(axiosMock.get).toHaveBeenCalledTimes(1)
  expect(axiosMock.get).toHaveBeenCalledWith(url)
  expect(getByTestId('greeting-text')).toHaveTextContent('hello there')
  expect(getByTestId('ok-button')).toHaveAttribute('disabled')
  // snapshots work great with regular DOM nodes!
  expect(container.firstChild).toMatchSnapshot()
  // you can also get a `DocumentFragment`, which is useful if you want to compare nodes across renders
  expect(asFragment()).toMatchSnapshot()
})

In the example you have three clear separations, prepare your component, make the action and wait to assert(Given, When, Then), and that it is. The test don't use anything that a normal user can't see, and the utility only return you this:

const {getByText, getByTestId, container, asFragment} = render(
    <Fetch url={url} />,
  )

Some functions to find inside the rendered component getByText and getByTestId, the Html DOM renderized on the container and a function asFragment to help you to make the snapshot, you can find the full api here

NOTE: Today I don't trust on snapshots because are hard to read, and most of the people(included myself) just make --update to fix problems, we're not machines to read that code autogenerated so I don't believe in the value that generates those snapshots, but if you feel comfortable doing it, you can do it.

As you can see, this library doesn't let you access the implementation, and sadly enzyme does. I decided to migrate to this new library no because of the hooks, the principal reason is that enzyme let me do it things that make me write wrong tests, is not enzyme fault, was my error. I always say that the libraries are tools, the quality of the code depends on 100% of the one who writes not the language, framework or library used.

So, now we're going to talk about the other side of the TDD, the refactor. Is a vital part of your job refactor the code when you finish your development, writing the test at the beginning helps you to understand the requirement and make the code works as you expected when you have that. Also, you can be sure your change won't affect the value that your code gives. You should be able to change everything inside your code if you have tests that always indicate you're in green, you can improve as many you want, and that is the beauty of good tests, is not just testing, is a safety net that protects my code from me.

Why refactor is related to TDD?

The refactor is a vital phase on the development, is on the refactor moment when you make your code not just fit the requirements, here you can improve the architecture, make it easy to extend, let more clear the responsibilities on the code, upgrade to new libraries or functionalities that allow you improve your code, like our example with the hooks, but you need to understand very well some rules before start refactoring:

  • A refactor should not change the interface of your software, if you need to change the behavior of your code, create tests, make it fails, then fix your code to make the test pass, and then refactor.
  • Never refactor anything that you don't understand often we found ourself dealing with black-box code, that anyone really understands what's doing, I could try to improve this code, but how can be sure that everything will work after your changes if you don't understand what should do in the first place?
  • Only refactor on green, you need to make sure that your changes are ok, so never try to improve code when your tests indicate that you're wrong, the key here is always coding doing baby steps, a small amount of code is the easy way to get control of your refactor, if you use git you can use fixup and autosquash to make easy the control of your changes, and then squash when you're satisfied with your changes.
  • If you don't trust your tests, don't refactor your code, this is very important if your tests don't give you the trust that you need, create the test that you need and then refactor.

How to really make a good test?

Now we're going to try a real-world exercise, we're going to continue with our problem trying to migrate to hooks and the tests made with enzyme.

We have this rule of trust on our test, but I don't trust my current tests, so what we're going to do is create new test focus on test DOM interaction instead of React instances.

This post will create tests for my an old dummy project called Brastlewark, this app is a simple CRA app that fetches a list of gnomes, then display the gnomes on a dashboard, you can filter and when you click a gnome then you can see the details of that gnome, the project uses redux and saga, let's check my first test, is a test to validate that the dashboard display none gnomes if any data is fetched.


describe("Dashboard", () => {
    let store;
    beforeEach(() => {
        const sagaMiddleware = createSagaMiddleware();
        store = createStore(rootReducer, applyMiddleware(sagaMiddleware));

        sagaMiddleware.run(function* fullSaga() {
            const rootWatcher = combineWatchers(rootSaga);
            const watchers = Object.keys(rootWatcher)
                .map(type => createActionWatcher(type, rootWatcher[type]));
            yield all(watchers);
        });
    });
    it("should render empty dashboard", () => {
        const { getByTestId } = render(
            <Provider store={store}>
                <Dashboard />
            </Provider>,
        );

        expect(getByTestId("empty-gnomes-container")).toBeDefined();
        expect(getByTestId("empty-gnomes-container").textContent).toEqual("No gnomes to display");
    });
});

NOTE: I add data-attributes to my react component to make it easy the tests, to fit with the new library I'm using data-testid to identify the elements on the UI.

My test passed, but now you can see that my test depends on more implementations details that before, my test now knows about redux and sagas, have middlewares and stores created, providers, is not just render. But this's not entirely wrong, because my tests depend on these things but they are outside of the component that I need to test. Is the minimal requirements I need to have to be able to render, my components are connected with redux and dispatch actions, with React testing library I just make sure to have the same basic things that the real applications have.

My test now doesn't verify what's inside of the component, I don't test what is the current state, or anything related with internal props, right now I invert the order of the dependencies on my tests.

What I should do next is create a utility that gives me those dependencies already loaded and ready to be used by my test to avoid duplication, I'm talking of something like this:

const renderWithState = (Component, props = {}) => {
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
    sagaMiddleware.run(function* fullSaga() {
        const rootWatcher = combineWatchers(rootSaga);
        const watchers = Object.keys(rootWatcher)
            .map(type => createActionWatcher(type, rootWatcher[type]));
        yield all(watchers);
    });
    const renderedOptions = render(
        <Provider store={store}>
            <Component {...props} />
        </Provider>,
    );
    return renderedOptions;
}

describe("Dashboard", () => {
    afterEach(cleanup);

    it("should render empty dashboard", () => {
        const { getByTestId } = renderWithState(Dashboard);

        expect(getByTestId("empty-gnomes-container")).toBeDefined();
        expect(getByTestId("empty-gnomes-container").textContent).toEqual("No gnomes to display");
    });
});

Now you can see that all the responsibility to create the store with redux and his sagas is on the renderWithState function, that I can extract to another file like a test-utility, my test now looks simpler, I provide the entire environment to the component that I want to test and I don't have to worry about implementations anymore.

My app only implements redux and saga, but the renderWithState function can implement and start anything that you need, you should include there all your base startup logic, like Context Providers (i18n, Styled Components, custom HOCs, React Router, etc), the Portals and everything that our application needs.

The real key here is defining the limitations or boundaries of your test, as you see now my tests are not unit tests, is a test who validate the business requirements, is most close to what BDD expect from our tests, but you can use this way to code with TDD, the important for us is that our tests become fasts, easy to write and easy to understand, is really important keep that in mind, because a test that is easy to understand is better than a hundreds of pages of documentation.

But well, right now we need to test more things, how can we pass values to the component? the code is dispatching a redux action listen for our saga and then call the endpoint to retrieve information, so what we need to do now is establish the yellow line when our test stop.

For this test the limit will be the endpoint call, we're going to get there and mock the fetch, the rest of the application should be tested with real conditions calling real actions and working like our real environment.

One thing that we're going to do is create new API that will retrieve important information for our test, this information will be the actions dispatched, I don't want to my test been using or implementing redux directly, to avoid test with implementations details I will create an Store Utils API, just like this:

class StoreUtil {
    actions = [];

    clearActions = () => {
        this.actions = []
    }

    pushAction = (action) => {
        this.actions.push(action);
    }

    getActions = () => {
        return this.actions;
    };

    getAction = (action) => {
        return new Promise(resolve => {
            let actionFound;
            while (!actionFound) {
                actionFound = this.actions.find(({ type }) => type === action);
            }
            resolve(actionFound)
        })
    }
}

This class is very simple, we have these actions, an we can:

  • Get all actions called.
  • Get one specific action.
  • Push one action to the registry.
  • Delete all actions.

The getAction is a promise because the actions dispatch process is async. When we render our app all the redux magic operates under the hood and the components are only updated when the reducers change his previous state if we don't make the promise and the while we will lose the actions that take longer than the first render.

NOTE: The Promise will wait forever to the element be displayed if the component is never rendered the jest timeout will stop the test and gives a failing test, you can improve this code to make it work better, but this fits perfectly for this post, so I will let it in this way, you can improve this to fit your needs.

I also create a new middleware that will listen to each action called, and push each one to the StoreUtil, and now our renderWithState include that middleware and return the storeUtil with the rest of rendered options.

const loggerMiddleware = (storeUtil) => store => next => action => {
    storeUtil.pushAction(action);
    next(action);
};

export const renderWithState = (Component, props = {}) => {
    const storeUtil = new StoreUtil();
    storeUtil.clearActions();
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(rootReducer, applyMiddleware(loggerMiddleware(storeUtil), sagaMiddleware));
    sagaMiddleware.run(function* fullSaga() {
        const rootWatcher = combineWatchers(rootSaga);
        const watchers = Object.keys(rootWatcher)
            .map(type => createActionWatcher(type, rootWatcher[type]));
        yield all(watchers);
    });
    const renderedOptions = render(
        <Provider store={store}>
            <Component {...props} />
        </Provider>,
    );
    return { ...renderedOptions, storeUtil };
}

NOTE: If you feel lost with the middleware, redux and saga terms, check these post that explain very good the basic and the complex.

And now in our test, we can verify that one action was called:

it("should dispatch the fetchGnomes Action", async () => {
        const { storeUtil } = renderWithState(Dashboard);

        const fetchGnomesAction = await storeUtil.getAction("FETCH_GNOMES");

        expect(fetchGnomesAction).toEqual({ "payload": { "params": {} }, "type": "FETCH_GNOMES" });
    });

The last assert of our test compare the action redux object, and this looks like an implementation detail for me, what we can do is replace this assert to check if the payload is called with the correct information, like this:

  it("should dispatch the fetchGnomes Action", async () => {
        const { storeUtil } = renderWithState(Dashboard);

        const fetchGnomesAction = await storeUtil.getAction("FETCH_GNOMES");

        expect(fetchGnomesAction.payload).toEqual({ "params": {} });
    });

Right now our test knows less about internals actions and models and just verifies the params who call the endpoint. This means that our test is verifying the code interfaces, and now gives more value making the test more easy to extend and understand.

The next part on our test verifies the boundaries and our interfaces, what I need now is retrieve information, so I need to mock the fetch API call to retrieve what I want I'm using the Javascript Fetch native API, and obviously, I don't want to my test cares about that, I always want to hide what I'm using to my test, because I could use Axios, request or any other library, my test should handle the mocks, without know which dependency I use, to make this I create a Wrapper called fetchApi that will make the call to the resource, this function is the only one who knows what I'm using to make my REST request:

export const fetchApi = (url, {
    method = 'GET',
    params,
    cache= 'no-cache',
    headers = {
        'content-type': 'application/json'
    },
    data
}) => {
    let paramText = queryString.stringify(params);
    paramText = paramText ? `?${paramText}` : '';

    return fetch(`${url}${paramText}`, {
        body: JSON.stringify(data),
        cache,
        headers,
        method, // *GET, POST, PUT, DELETE, etc.
    }).then(response => {
        return response.json();
    }).catch(error => { 
        return { error }; 
    });
};

I'm going to create a new fectApi test util to be able to mock this and to set mocked answers to my tests.

export class FetchUtilsMock {
    mockedFetch;
    constructor(fetchApi) {
        this.mockedFetch = fetchApi.mockReset();
    }

    setResponse = (payload) => {
        this.mockedFetch.mockReturnValue(payload)
    }
}

Is a simple function that will store the mock, and then we can mock the responses that we want, the constructor reset the mock to avoid problems among tests, and you can call the set response method every time you need, the mockReturnValue is a function that the jest mocks allows implementing.

import fetchApi from '../../utils/api-utils';

jest.mock('../../utils/api-utils');

const emptyResponse = {
    "Brastlewark": []
}

describe("Dashboard", () => {
    let fetchUtil;

    afterEach(cleanup);

    beforeEach(() => {
        fetchUtil = new FetchUtilsMock(fetchApi);
    })

    it("should render empty dashboard", () => {
        fetchUtil.setResponse(emptyResponse);
        const { getByTestId } = renderWithState(Dashboard);

        expect(getByTestId("empty-gnomes-container")).toBeDefined();
        expect(getByTestId("empty-gnomes-container").textContent).toEqual("No gnomes to display");
    });

This's how the test looks now, I'm mocking my api-utils with jest.mock('../../utils/api-utils');, on the beforeEach, I instance the mock utility and then each test will define the response. I'm mocking right now an empty response, but we can mock multiple scenarios and responses, our test now allows us to test different possible (and real-live) responses to test our application.

You can mock any other integration that you have on your application like this, from a REST request, Databases, Redis, a Queue or whatever you need. The important here is to always wrap your integrations boundaries, to make it easy to test and develop, with this strategy you can change your dependencies without to refactor your entire application.

The next logical step is to mock a happy-path scenario, I will set the response with valid data and then validate that the gnomes are displayed, I will use a utility from react-testing-library called waitForElement, you also have others async-await dom related tools to make your test here, this will wait for the element to be displayed and return the component who has the data-testid="gnome-box-container"

const correctAnswer = {Brastlewark: [...]} // mock data with valid information

it("should dispatch the gnomes", async () => {
        fetchUtil.setResponse(correctAnswer);
        const { getByTestId } = renderWithState(Dashboard);

        const boxContainer = await waitForElement(() => getByTestId("gnome-box-container"));

        expect(boxContainer.children.length).toEqual(correctAnswer.Brastlewark.length);
    });

I will move the correctAnswer and the emptyAnswer constants to a file where I can isolate my mocked data, in that way if the model changes, I just need to update one file and all tests of my application should don't have the responsibility to create the data.

Always tests before refactor

As you can see, I'm just creating tests for my existing code, I'm writing tests to verify that my code works as I expected, and then I will move to the hooks. To my new tests, the details of which library I 'm using is not relevant, they only care about, display or not display something on the DOM, next we're going to tests interactions, clicking and submitting data, but before I will check my coverage, I use the same reporter that CRA3.0 gives me for jest, lets check it:


NOTE: To be able to use CRA coverage report I create a script on my package.json like this: "test:ci": "npm test -- --coverage --watchAll=false",

As you can see, my coverage is very low, but I'm sure that my tests are good, and at least the things that I test are working as I espected, the coverage is an indicator of different values, the branches are telling us that we have a lot of switches, if, for loops, etc. and we are not testing all the possible scenarios, get 100% of coverage in most of the cases is now worth it, a good exercise for us as developers is read these reports, and verify if you really need that conditions to be tested, in some cases you will find that the code is protecting you from a condition that's impossible to happen, don't try to reach a 100% just because is the rule, try to cover the most real scenarios as you can, understand the cases and then refactor or test it if you feel that you must.

Let's go with interactions

A UI is more than just display, we have interactions, but how can we test it? One normal case for me at the past was use enzyme instance of the shallow component that makes something like this:

const wrapper = mount(<Stateful />);
const instance = wrapper.instance();

instance.clickButton(); // Internal method

expect(...).toEqual(...);

This gives me the coverage and in a way, I was testing the button click, what's wrong with this approach? well, I'm using the clickButton method and my test is never really clicking anything, I was wrong to marry my test with internal methods because now I want to migrate to a functional component and this test doesn't support that, my test is blocking me to improve my code.

Another thing very common on my tests with enzyme is this:

const wrapper = mount(<Foo />);

expect(wrapper.find(<Clicks />).children.length).to.equal(0);
wrapper.find('a').simulate('click');
expect(wrapper.find(<Clicks />).children.length).to.equal(1);

this's my close to a good thing, I'm looking for a component inside the Foo and then verifies his children on the DOM, I simulate a real click on the wrapper and I don't care about internal methods, is a good step to a better test, but one thing is wrong, I'm assuming that <Clicks /> is going to be inside Foo if I change the component I will have to change it on all the tests who use this, and also I'm assuming that the a element exists, if on the future the a becomes a button will break my tests, when I should not care about which html element I'm clicking. Here even in a better test, I'm depending on an internal implementation to make my tests pass.

To improve these tests you can do something like this:

const wrapper = mount(<Foo />);

expect(wrapper.find('[data-testid="clicks-container"]').children.length).to.equal(0);
wrapper.find('wrapper.find('[data-testid="clicks-action"]'').simulate('click');
expect(wrapper.find(wrapper.find('[data-testid="clicks-container"]').children.length).to.equal(1);

Now I'm based my test on data-testid, both are abstractions, clicks-container is representing something where the information his children's will indicate how many clicks I made, and the clicks-action is a representation of a clickable element, I don't care about which type, just the fact that's clickable matters on my tests.

You can see how I improve my test using enzyme, to let clear that you don't have to migrate to a new library to write better tests, the real importance here is how you write your test, how clear they are, how isolated the runs are, not the library used.

With react testing library you have the fireEvent, that simulate the events on the DOM, is a very powerful utility, check his documentation here, my test is going to find the input, then change the input value to the first gnome name value and then verifies that the only the correct gnome is displayed.

 it('should filter the gnomes', async () => {
    fetchUtil.setResponse(correctAnswer);
    const { storeUtil, getByTestId } = renderWithState(Dashboard);
    const gnomeName = correctAnswer.Brastlewark[0].name;
    const gnomeId = correctAnswer.Brastlewark[0].id;
    const filter = await waitForElement(() =>
      getByTestId('gnomes-filter-input')
    );

    fireEvent.change(filter, { target: { value: gnomeName } });

    await storeUtil.getAction('GNOMES_FILTERED');
    const boxContainer = await waitForElement(() =>
      getByTestId('gnome-box-container')
    );
    expect(boxContainer.children.length).toEqual(1);
    const gnomeDetails = await waitForElement(() =>
      getByTestId(`gnome-box-item-${gnomeId}`)
    );
    expect(gnomeDetails.textContent).toEqual(gnomeName);
  });
  • Given I receive the correct information, and I have the input to filter the gnomes.
  • When I search for my gnome
  • Then I see only that gnome

As you can see my test follow the pattern Given-When-Then and I verify that the business requirements are delivered on my code. Now I can start migrating my code to hooks and the tests should not break.

Mutants on the code and the corner cases

Let's assume that we are in normal flow, and you need to code a requirement, the requirement has 3 acceptance criteria that you need to deliver, you test it and coded and the 3 original requirements are already developed, very often you found that there are more things that just 3 requirements, you have weird cases that you need to validate to avoid bugs in the future.

One thing that you need to validate as a developer is that you code supports these weird corner cases if you have any doubt about which behavior should have on these new scenarios you need to talk to the one who will receive the development (PO, ProxyPo, Stakeholder, client, etc) and he as the owner should indicate you which path follow, but in any case you should ignore this, if you feel that the code needs a test to validate a corner case, you must create the test an add to the code, because this will create more pain in the future when you or anyone else don't understand the reason or the why behind these corners cases.

TDD helps you to develop with control, BDD helps you to understand the business, but sometimes you need to just make tests to verify that the code works when something is not as normal you expect, always remember Murphy's law: "things will go wrong in any given situation, if you give them a chance".

The mutants are a different topic, a mutant generation is a strategy of testing where you modify intentionally your code, and check if the tests are ok, if you change something on your code like, remove a line, change a > to a =>, include a "!" before an assertion, and then your tests indicate that everything is still ok, your code is wrong. Is a healthy process test mutants on your code and check how robust is your suite of tests, there are some libraries to help you with this, Stryker js is one of the most popular out there, you need to take all these in count when you test your application, each type of test gives a different value and all this helps you to be a better developer.

Conclusions

Today we test it a React application with react testing library, simulating a real-live environment, we talk about the importance of good test to create maintainable, extensible and comprehensible code, the importance of having implementations details outside the tests, and how to mock our boundaries and let our app behave as a normal application, if we keep improving our tests we will found a safety net to let us implement, play and get fun while we build amazing applications.

Take in account that I use terms like scenarios, responsibility, no implementations details on tests, mock, utils to create mocks and others, this vocabulary is something that all the team of devs should know and handle. When a team understands why these words matters you can say that your team has a Culture of Testing that will let you go to the weekends trusting more on your code.

InTheTestsWeTrust

Check my previous posts

Discussion (0)

pic
Editor guide