FormidableLabs / nextjs-sanity-fe

NextJS Demo site with Sanity CMS
https://nextjs-sanity.formidable.dev/
29 stars 6 forks source link

E2E Tests: Consolidated "real" and "mock" tests #155

Closed scottrippey closed 1 year ago

scottrippey commented 1 year ago

What

Consolidates the "real" and "mock" tests into a single file, since they usually share the vast majority of logic. Now, our tests can have inline logic that differs based on "real" or "mock" scenarios.

Also, adds skipReason to provide a "reason" for skipped tests.

Testing

Verify that all "real" and "mock" tests pass in CI

vercel[bot] commented 1 year ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated
nextjs-sanity-fe ✅ Ready (Inspect) Visit Preview Oct 24, 2022 at 10:14PM (UTC)
kgpax commented 1 year ago

Looks good. I really like the skipWhen utility!

The only thing I'd suggest for this is to allow realOnly and mockOnly to work outside of the context of a test runner operation; i.e., within a given test, to differentiate values or assertions where steps are the same for both.

The following is slightly adapted from the README of the project that I've introduced mocks to, and gives examples of the various use cases:

import { withMocks } from '../support/commands';

withMocks(({ realOnly, mocksOnly, skipMocks }) => {
  describe('My tests which support both mock data and real data 💪', () => {

    // this tests runs for both mock-data and real-data runs
    it('should display menu', () => {
      cy.getByTestId('main-nav-bar').should('be.visible');
    });

    realOnly(() => {
      // this test runs only in real-data runs.
      // note that we cannot be too explicit with the setup or assertions, since the data could change
      it('should display menu', () => {
        cy.getByTestId('main-nav-category-item').first().realHover();
        cy.getByTestId('secondary-nav-menu').should('be.visible');
      });
    });

    mockOnly(() => {
      // this test runs only in mock-data runs.
      // given that the mock data is known and predictable, we can be much more explicit in our tests
      it('should display expected menu when hovering over "Women"', () => {
        cy.getByTestId('main-nav-category-item').contains('Women').realHover();
        cy.getByTestId('secondary-nav-menu').find('a').should('have.length', 14);
        cy.getByTestId('secondary-nav-menu').find('a').contains('Shoes').should('be.visible');
        // etc...
      });
    });

    // this test runs for both mock-data and real-data runs, but the implementation varies
    it('should add product to basket and allow user to pay by card', () => {
      realOnly(() => cy.addProductToCart('123456'));
      mockOnly(() => cy.addProductToCart(mockProducts.standardProduct));

      cy.getByTestId('main-nav').find('[data-test-id="cart-icon"]').click();
      cy.getByTestId('checkout-button').should('be.visible').click();

      realOnly(() => cy.getByTestId('shipping-address').should('be.visible').fillAddress(realData.address1));
      mockOnly(() => cy.getByTestId('shipping-address').should('be.visible').fillAddress(mockAddresses.address1));

      cy.getByTestId('make-payment-button').should('be.enabled').click();

      realOnly(() => cy.getByTestId('payment-details').should('be.visible').payByCard(realData.visaCard1));
      mockOnly(() => cy.getByTestId('payment-details').should('be.visible').payByCard(mockCreditCards.visa));

      cy.getByTestId('confirm-order-button').should('be.enabled').click(); 

      cy.expectUrl('/order-confirmation');
    });

    // this test runs for both mock-data and real-data runs, but will bypass the mock for the
    // "ActiveCart" operation.
    // this may be because you need real data to support the whole flow being tested, where the
    // whole flow may not yet be fully mocked
    // you can specify multiple operations by supplying an array of operation names instead
    skipMocks('ActiveCart', () => {
      it('should display how many items are in the basket', () => {
        cy.getByTestId('cart-badge').should('have.text', '0')
      });
    });
  });
});
scottrippey commented 1 year ago

@kgpax I agree, I love the granularity you've got there. So I did add a mockOnly.isActive (and realOnly.isActive) flag, so that I can do this too. For example, I have if (mockOnly.isActive) ... in one place, to make extra assertions based on the mock data. I only used it once, but I imagine it would be very useful if we had a lot more tests.

Mostly, though, I try to stick to using the "chaining syntax" (eg mockOnly.before), because it's so clean, and doesn't require any extra nesting.

scottrippey commented 1 year ago

Looking for a review and a ✅ , if anyone is available for that?

scottrippey commented 1 year ago

Absolutely! Kevin and I have already discussed co-authoring a multi-part blog post. This was our PoC for some of our ideas! (Plus, we needed something OSS)