• No results found

Our Testing Strategy

In document (Full Stack React)-BuildingYelp.pdf (Page 40-47)

We’ll use expect() to set up our expectations and shallow() (from enzyme) to render our elements into the test browser.

Our Testing Strategy

When testing any code in any language, the principles of testing are pretty much the same. We want to:

1. define the focused functionality 2. set an expectation of the output

3. compare the executed code with the test code

In Jasmine, each of these steps are well-defined:

1. Using describe()/it() defines the functionality 2. We’ll use expect() to set the expectation

3. We’ll use beforeEach() and matchers to confirm the output

To set up our our test, we’ll need to pretend we’re rendering our <App /> component in the browser.

Enzyme makes this easy regardless of handling shallow or deep rendering (we’ll look at the difference later).

In our test, we can shallow render our <App /> component into our browser and store the result (which will be our rendered DOM component). Since we’ll want a “clean” version of our component every time, we need to do this in the beforeEach() block of our test:

// ...

describe('<App />', () => { // define our tests in here

let wrapper; // "dom" node wrapper element beforeEach(() => {

wrapper = shallow(<App />);

}) })

With our test set up, we can test that our <App /> component contains a Router component by using the find() method in our wrapper instance. To set the expectation in our component, we’ll use expect():

// ...

describe('<App />', () => { // ...

it('has a Router component', () => { expect(wrapper.find('Router')) .to.have.length(1);

}) })

Finally, we can run our tests by using our npm script we previously built:

$ npm run test

Since we haven’t implemented the <App /> component, our test will fail. Let’s turn our test green.

Routing

Before we implement our routes, let’s take a quick look at how we’ll set up our routing.

When we mount our react app on the page, we can control where the routes appear by using the children to situate routes where we want them to appear. In our app, we’ll have a main header bar that we’ll want to exist on every page. Underneath this main header, we’ll switch out the content for each individual route.

We’ll place a <Router /> component in our app as a child of the component with rules which designate which children should be placed on the page at any given route. Thus, our <App />

component we’ve been working with will simply become a container for route handling, rather than an element to hold/display content.

Although this approach sounds complex, it’s an efficient method for holding/generating routes on a per-route basis. It also allows us to create custom data handlers/component generators which come in handy for dealing with data layers, such as Redux.

With that being said, let’s move on to setting up our main views.

In our src/containers/App.js, let’s make sure we import the react-router library.

import React, { PropTypes } from 'react';

import { Router } from 'react-router';

// ...

Next, in our usual style, let’s build our React component (either using the createClass({}) method we used previously or using the class-based style, as we’ll switch to here):

import React, { PropTypes } from 'react';

import { Router } from 'react-router';

class App extends React.Component { render() {

return (<div>Content</div>) }

}

We like to include our content using the classical getter/setter method, but this is only a personal preference.

import React, { PropTypes } from 'react' import { Router } from 'react-router'

class App extends React.Component {

We’ll use our app container to return an instance of the <Router /> component. The <Router />

component require us to pass a history object which tells the browser how to listen for the location object on a document. The history tells our react component how to route.

There are multiple different types of history objects we can use, but the two most popular types are the browserHistory and hashHistory types. The browserHistory object uses the native html5 react router to give us routes that appear to be server-based.

On the other hand, the hashHistory uses the # sign to manage navigation. Hash-based history, an old trick for client-side routing is supported in all browsers.

We’ll use the browserHistory method here. We need to tell the <Router /> instance we want to use the browserHistory by passing it as a prop in our routing instance:

import React, { PropTypes } from 'react';

import { Router } from 'react-router';

class App extends React.Component { static propTypes = {

history: PropTypes.object.isRequired }

// class getter get content() { return (

<Router history={this.props.history} />

) }

render() { return (

<div style=\{\{ height: '100%' \}\}>

{this.content}

</div>

) } }

We’re almost ready to place our routes on the page, we just have to pass in our custom routes (we’ll make them shortly). We’ll wrap our routes into this <App /> component:

import React, { PropTypes } from 'react';

import { Router } from 'react-router';

class App extends React.Component { static propTypes = {

routes: PropTypes.object.isRequired, history: PropTypes.object.isRequired }

In order to actually use our <App /> component, we’ll need to pass through the two props the component itself expects to receive when we render the <App /> component:

history - we’ll import the browserHistory object from react router and pass this export directly.

routes - we’ll send JSX that defines our routes

Back in our src/app.js file, we’ll pass through the history directly as we import it.

import React from 'react'

import ReactDOM from 'react-dom'

import 'font-awesome/css/font-awesome.css' import './app.css'

import {browserHistory} from 'react-router' import App from 'containers/App/App'

const mountNode = document.querySelector('#root');

ReactDOM.render(

<App history={browserHistory} />, mountNode);

Lastly, we’ll need to build some routes. For the time being, let’s get some data in our browser. Let’s show a single route just to get a route showing up. We’ll revise this shortly.

To build our routes, we need access to the:

<Router /> component

<Route /> component

Our custom route components.

The Router and Route component can be imported directly from react-router:

import React from 'react'

import ReactDOM from 'react-dom'

import 'font-awesome/css/font-awesome.css' import './app.css'

import {browserHistory, Router, Route} from 'react-router' import App from 'containers/App/App'

const mountNode = document.querySelector('#root');

ReactDOM.render(

<App history={browserHistory} />, mountNode);

We can create our custom route by building a JSX instance of the routes using these two components:

// ...

import './app.css'

import {browserHistory, Router, Route} from 'react-router'

const routes = ( <Router>

<Route path="/" component={Home} />

</Router>

) //...

Since we haven’t yet defined the Home component above, the previous example fails, so we can create a really simple to prove it is working:

// ...

import './app.css'

import {browserHistory, Router, Route} from 'react-router'

const Home = React.createClass({

render: function() {

return (<div>Hello world</div>) }

})

const routes = ( <Router>

<Route path="/" component={Home} />

</Router>

) //...

Finally, we can pass the routes object into our instance of <App /> and refreshing our browser.

Provided we haven’t made any major typos, we’ll see that our route has resolved to the root route and

“Hello world” is rendered to the DOM.

We also see that our tests pass as the <App /> component now has a single <Router />

component being rendered as a child.

In document (Full Stack React)-BuildingYelp.pdf (Page 40-47)

Related documents