요즘 한창 TDD에 관심이 늘었습니다 🤔

저도 몸소 체감하고 싶은데, 쉽지 않지만 재밌네요 ㅎㅎㅎ

천천히 따라해봅시다 :)


image.png

1. Header

1. CRA

간단하게 CRA로 시작하겠습니다.

npx create-react-app testing-with-react

먼저 App.js, Index.js 파일만 두고 src내의 파일을 삭제합니다.

이후, 프로젝트에서 사용할 패키지를 설치합니다.

yarn add -D enzyme enzyme-adapter-react-16 jest jest-enzyme

yarn add node-sass

index.js 파일의 내부에 필요없는 부분을 지워줍니다.

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

2. header/index.js, styles.scss

이제 assets/graphics 폴더를 만들고, 헤더를 만들때 사용할 로고사진을 넣습니다.

다음 components/header폴더를 만들고, index.js, styles.scss파일을 생성합니다.

원래라면 테스트를 먼저 해야하지만, 처음이므로 UI를 먼저 생성해보겠습니다.

// components/header/index.js
import React from 'react';
import './styles.scss';
import Logo from './../../assets/graphics/logo.png';

const Header = props => {
  return (
    <header data-test='headerComponent'>
      <div className='wrap'>
        <div className='logo'>
          <img data-test='logoIMG' src={Logo} alt='logo' />
        </div>
      </div>
    </header>
  );
};

export default Header;

className으로 테스트에 사용하는 것은 부적절하기에 data-test로 명명하였습니다.

이후 test파일에서 사용하는 법을 알게됩니다.

각 태그에 맞게 scss 디자인을 꾸며줍니다.

header {
  display: block;
  width: 100%;
  height: 50px;
  margin: 0 auto;
  padding: 0 10px;
  background: #121212;

  .wrap {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
    margin: 0 auto;
    max-width: 980px;

    .logo {
      display: block;
      width: 120px;
      position: absolute;
      left: 10px;
      top: 50%;
      -webkit-transform: translateY(-50%);
      -moz-transform: translateY(-50%);
      transform: translateY(-50%);

      img {
        display: block;
        width: 100%;
        margin: 0;
      }
    }
  }
}

3. Add font & app.scss

구글 폰트를 적용할 예정이므로 public/index.html의 헤더에 아래를 추가하겠습니다.

<link
  href="https://fonts.googleapis.com/css?family=Open+Sans:400,700"
  rel="stylesheet"
/>
`

app.scss파일을 생성하여 App.js의 디자인도 수정하겠습니다.

body {
  font-family: 'Open Sans', sans-serif;
  font-size: 18px;
  line-height: 22px;
  color: #121212;
  margin: 0;
  padding: 0;
  background: #707174;
}

4. Add Header to App.js

이제 App.js에서 Header를 불러와주겠습니다.

// App.js
import React from 'react';
import './app.scss';
import Header from './components/header';

function App() {
  return (
    <div className='App'>
      <Header />
    </div>
  );
}

export default App;

자, 이제 yarn start로 확인해보면 처음 사진에서 처럼 헤더가 보이실겁니다.

5. Test Header component

테스트를 위해 가장 먼저 루트 디렉토리에 setupTest.js파일을 생성합니다.

테스트파일에서 이를 불러와서 사용합니다. 기본 설정으로 보시면 됩니다.

// setupTest.js
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({
  adapter: new Adapter(),
  disableLifecycleMethods: true
});

이제 Test를 위해 header폴더에 header.spec.js 파일을 생성합니다.

그리고 루트디렉토리에 utils폴더를 생성하고 index.js파일을 생성합니다.

utils에는 공통으로 사용할 테스트 메소드를 작성해줄겁니다.

findByTestAtrr 메소드에서 사용하는 wrapper가 아까 전의 data-test를 사용합니다.

value값에 반드시 따옴표를 붙여주셔야 합니다!

// utils/index.js
export const findByTestAtrr = (component, attr) => {
  const wrapper = component.find(`[data-test='${attr}']`);
  return wrapper;
};

이제 테스트파일을 작성해보겠습니다.

헤더가 에러없이 렌더되는지, 로고가 렌더되는지를 확인합니다.

// components/header/header.spec.js
import React from 'react';
import { shallow } from 'enzyme';
import '../../setupTest';
import Header from './index';
import { findByTestAtrr } from '../../utils';

const setUp = (props = {}) => {
  const component = shallow(<Header {...props} />);
  return component;
};

describe('Header Component', () => {
  let component;
  beforeEach(() => {
    component = setUp();
  });

  it('Should render without errors', () => {
    const wrapper = findByTestAtrr(component, 'headerComponent');
    expect(wrapper.length).toBe(1);
  });

  it('Should render a logo', () => {
    const logo = findByTestAtrr(component, 'logoIMG');
    expect(logo.length).toBe(1);
  });
});

작성이 되었으면 yarn test로 확인해봅니다.

이미 렌더를 해보았기에 테스트가 성공할 것을 알고 있습니다...!
image.png


2. Headline

1. index.js

이제 헤더 아래에 들어갈 헤드라인을 생성해보겠습니다.

components/headline폴더에 index.js, styles.scss, headline.spec.js를 생성합니다.

scss는 필요 시에 사용하고, 다른 부분부터 작성하겠습니다.

이번에도 data-test를 활용해서 생성해보겠습니다.

// components/headline/index.js
import React, { Component } from 'react';
import './styles.scss';

class Headline extends Component {
  render() {
    const { header, desc } = this.props;
    if (!header) return null;
    return (
      <div data-test='HeadlineComponent'>
        <h1 data-test='header'>{header}</h1>
        <p data-test='desc'>{desc}</p>
      </div>
    );
  }
}

export default Headline;

2. App.js, app.scss

App.js와 app.scss 파일을 수정합니다. Headline을 불러오고 props를 전달합니다.

// App.js

// ... 생략 ...
function App() {
  return (
    <div className='App'>
      <Header />
      <section className='main'>
        <Headline header='Posts' desc='Click the button to render Posts!' />
      </section>
    </div>
  );
}
// ... 생략 ...
// app.scss

// ... 생략 ...
.main {
  width: 100%;
  max-width: 980px;
  margin: 50px auto 80px;
  padding: 0 10px;
}

3. headline.spec.js

이제 headline.spec.js파일을 작성해보겠습니다.

이번에도 비슷합니다. 에러없이 렌더가 잘 되는지, 각 태그가 렌더되는지 등입니다.

마지막 describe는 if(!header) return null에 대응합니다.

// components/headline/headline.spec.js
import React from 'react';
import { shallow } from 'enzyme';
import Headline from './index';
import '../../setupTest';
import { findByTestAtrr } from '../../utils';

const setUp = (props = {}) => {
  const component = shallow(<Headline {...props} />);
  return component;
};

describe('Headline Component', () => {
  describe('Have props', () => {
    let wrapper;

    beforeEach(() => {
      const props = {
        header: 'Test Header',
        desc: 'Test Desc'
      };
      wrapper = setUp(props);
    });

    it('Should render without errors', () => {
      const component = findByTestAtrr(wrapper, 'HeadlineComponent');
      expect(component.length).toBe(1);
    });

    it('Should render a h1', () => {
      const h1 = findByTestAtrr(wrapper, 'header');
      expect(h1.length).toBe(1);
    });

    it('Should render a desc', () => {
      const desc = findByTestAtrr(wrapper, 'desc');
      expect(desc.length).toBe(1);
    });
  });

  describe('Have No props', () => {
    let wrapper;
    beforeEach(() => {
      wrapper = setUp();
    });

    it('Should not render', () => {
      const component = findByTestAtrr(wrapper, 'HeadlineComponent');
      expect(component.length).toBe(0);
    });
  });
});

yarn testyarn start로 확인합니다.

image.png