I would like to solve the problem of stubs in a way that allows us to easily extend them. I would also like to prevent the stubs to go stale and I would like to have it defined in a central place. Let me show you what I have in mind on the next files.
Last active
December 10, 2018 13:00
-
-
Save DanielMSchmidt/a6fb42ae798f019be34a27a3c325a388 to your computer and use it in GitHub Desktop.
This is a rough draft of how we could define mocks in a nice way
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Cluster = require("#mocks/definitions/cluster"); | |
export function DefaultCluster({ children, applications, clusterProps = {} }) { | |
const hostname = "127.12.12.4"; | |
return ( | |
<Cluster {...clusterProps}> | |
<Node hostname={hostname}> | |
{/* My point is we COULD use mechanisms like this, we don't need to. It empowers us IMHO */} | |
{React.Children.map(applications(), element => | |
React.cloneElement(element, { hostname }) | |
)} | |
</Node> | |
{children} | |
</Cluster> | |
); | |
} | |
export function SingleNodeCluster({ children }) { | |
return <DefaultCluster applications={() => children} />; | |
} | |
export function TwoNodeCluster({ | |
firstNodeApplications, | |
secondNodeApplications | |
}) { | |
return ( | |
<DefaultCluster applications={firstNodeApplications}> | |
<Node hostname="127.12.12.5"> | |
{React.Children.map(secondNodeApplications(), element => | |
React.cloneElement(element, { hostname }) | |
)} | |
</Node> | |
</DefaultCluster> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { setStub, setStubWithAdapter } = require("magic-lib"); | |
const DefaultCluster = require("#mocks/default-cluster"); | |
const DefaultApp = require("#mocks/default-app"); | |
describe("Service", () => { | |
it("has a started service", () => { | |
const stub = setStub( | |
<DefaultCluster | |
renderApplications={() => <DefaultApp healthStatus="healthy" />} | |
/> | |
) | |
); | |
}); | |
it("runs on different services on different nodes", () => { | |
const stub = setStub( | |
<DefaultCluster | |
renderApplications={() => <DefaultApp healthStatus="healthy" />} | |
> | |
{/* This could also be another import if you use it multiple times */} | |
<Node hostname="8.8.8.8"> | |
<Application id="/my-other-service"> | |
<Task status="running" /> | |
</Application> | |
</Node> | |
</DefaultCluster> | |
) | |
); | |
// ... assertions | |
}); | |
it("runs same service on different nodes in different zones", () => { | |
/* Although this seems like a lot of code we could leverage code reuse here. | |
What I would like to highlight here is we have the possibility to clearly define | |
what we need from a mock in a descriptive way and fine-grained. A first time reader | |
will immediately understand which cluster we currently have in our mock. The magic is | |
how it happens, not what happens. | |
*/ | |
const stub = setStub( | |
<Cluster> | |
<Node hostname="9.9.9.9" region="us-west-1" zone="us-west-1a"> | |
<Application id="/my-service"> | |
<Task status="running" /> | |
</Application> | |
</Node> | |
<Node hostname="8.8.8.8" region="eu-west-3" zone="eu-west-3c"> | |
<Application id="/my-service"> | |
<Task status="running" /> | |
</Application> | |
</Node> | |
</Cluster> | |
); | |
// ... | |
}); | |
}); | |
describe("Possible further extensions", () => { | |
// I would like to show you how we could extend this solution in crazy ways. | |
// Do we want this? I don't know, but I would love to be enabled to do it if I choose to want it. | |
it("could have callbacks", () => { | |
const mockApplicationCreation = jest.fn(); | |
const stub = setStub( | |
({ withApp }) => ( | |
<DefaultCluster | |
renderApplications={() => | |
withApp ? <DefaultApp healthStatus={healthStatus} /> : null | |
} | |
clusterProps={{ | |
onAppCreate: mockApplicationCreation | |
}} | |
/> | |
), | |
{ | |
withApp: false | |
} | |
); | |
// Do action that "creates" an app | |
expect(mockApplicationCreation).toHaveBeeenCalled(); | |
mockApplicationCreation.resetMocks(); | |
stub.setProps({ | |
withApp: true | |
}); | |
// Do action that "creates" an app, should not send a request because the app already exists | |
expect(mockApplicationCreation).not.toHaveBeeenCalled(); | |
}); | |
it("could have adapters", () => { | |
const setStubForMockServer = setStubWithAdapter("mockserver"); | |
const setStubForDyson = setStubWithAdapter("dyson"); | |
const setStubForUnicornSolutionThatSolvesAllOurProblems = setStubWithAdapter( | |
"unicorn" | |
); | |
}); | |
it("could have a dynamic approach to changes", () => { | |
const stub = setStub( | |
({ healthStatus }) => ( | |
<DefaultCluster | |
renderApplications={() => <DefaultApp healthStatus={healthStatus} />} | |
/> | |
), | |
{ | |
healthStatus: "healthy" | |
} | |
); | |
// Cypress assertion that something is like it should be | |
stub.setProp({ healthStatus: "unhealthy" }); | |
// Cypress assertion that something is like it should be | |
stub.reset(); | |
}); | |
}); |
- We can keep up with new functionality of our services by extending the Cluster component with some additional mocks
- We can easily define defaults in a dynamic and extensible way without having too much magic in place
- We can define different renderers / adapters for different mock servers (mock-server / dyson / ...)
- The solution is very extensible in a lot of directions (making it more dynamic, ...)
- We have one central place to keep up with changes of the APIs we consume not 20, therefore our stubs are not so likely to go stale.
- We can choose the granularity on which we want our mocks to be defined. We can start with very little props and more "scenario"-like outputs and move step by step to the granularity we need
- JSX always gives the notion of being flexible / changing. It is meant here as only being a static way to describe the current state in the very same file as the tests
- A little bit of magic is involved by this layer of abstraction, at least on how it happens
We would need to write a react-renderer. There are a lot of examples that we could take a look at. There is a step by step guide how to do it. If we can find a good abstraction as a basis for defining stubs for different routes as a react element we only need to translate to API calls to the mock server.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Looks like a misuse of JSX to me. Maybe XML is what you want :)
Obviously you have 5 pros and 1 con. I'd challenge you to think what if it doesn't make sense?